[Java 기초] 상속

Updated:

이번 포스트에서는 자바의 상속에 대해서 알아보도록 하겠다. 👨‍🏫

📋 목차

  • 자바 상속의 특징
  • Object 클래스
  • 추상 클래스
  • 메소드 오버라이딩
  • super 키워드
  • final 키워드
  • Dynamic Method Dispatch
  • instanceof 연산자

자바 상속의 특징

상속 : 기존의 클래스를 재사용하여 새로운 클래스를 정의하는것.

  • 코드의 추가, 변경이 쉽다.
  • 코드의 재사용성을 높여 생산성에 기여.
  • 코드의 중복성을 제거하여 유지보수 유리.
  • 자손 클래스는 조상 클래스의 모든 멤버를 상속받는다.
  • 생성자와 초기화 블럭은 상속되지 않는다.

같은 내용의 코드를 하나 이상의 클래스에 중복적으로 추가해야 하는 경우에는 상속관계를 이용하여 중복을 최소화 해야한다.

$($자식 클래스 여러개에 똑같은 코드를 추가하지 않고 하나의 부모 클래스에 코드를 추가하는것이 바람직하다.)

class Parents { } class Child extends Parents { }

새로 생성할 클래스이름 뒤에 extends 상속받을기존클래스이름 으로 정의

class Tv {
    boolean power;
    int channel;

    void power() { power = !power; }
    void channelUp() { channel++; }
    void channelDown() { channel--; }
}

// Tv 클래스로 부터 모든 멤버를 상속받는다.
class CaptionTv extends Tv {
    boolean caption;
    void displayCaption(String text) {
        if(caption) {
            System.out.println(text);
        }
    }

    CaptionTv() {
      this(10);
    }

    CaptionTv(int channel) {
        this.channel = channel;
    }
}

class Example {
    public static void main(String[] args) {
        CaptionTv ctv = new CaptionTv(10);
        ctv.channelUp();
        System.out.println(ctv.channel);
        ctv.displayCaption("Hello World"); // cation = false여서 출력안됨

        ctv.caption = true;
        ctv.displayCaption("Now turned on");
    }
}

자손 클래스의 인스턴스를 통해 조상 클래스의 인스턴스 없이도 사용가능.

Object 클래스

최상위의 조상클래스이다.

어떤 클래스로 부터도 상속 받지 않는 모든 클래스가 자동적으로 Object클래스를 상속 받게 된다.

추상 클래스 abstract

추상화 : 클래스간의 공통점을 찾아내서 공통의 조상을 만드는 작업

구체화 : 상속을 통해 클래스를 구현, 확장하는 작업

추상클래스는 미완성 메서드를 포함하고 있기때문에 추상클래스는 인스턴스를 생성할수 없다.

대신 상속을 통해서 자손클래스에 의해 완성된다.

추상 메서드는 선언부만 존재하고 상속받는 자손클래스에서 몽통을 오버라이딩으로 구현해주어야한다.

만약 조상으로부터 상속받은 추상메서드를 자손클래스에서 전부 구현하지 못한다면 자손클래스도 추상클래스로 지정해줘야한다.

abstract 리턴타입 메서드이름();

abstract class ChessPiece {
  int x, y;
  abstract void move(int x, int y);
  void stop() { /*현재 위치에서 정지 */ }
}

class Queen extends ChessPiece {
  void move(int x, int y) { /* 지정된 위치로 이동 */}
  void changePosWithKing()
}

class King extends ChessPiece {
  void move(int x, int y) { /* 지정된 위치로 이동 */}
  void gameOver()
}

오버라이딩

조상 클래스로부터 상속받은 메서드의 내용을 변경하는것

예제

class Parent {
  String lastName;

  void myName(String firstName) {
    System.out.println("저는 부모이고 이름은 " + lastName + firstName + "입니다.");
  }
}

class Child extends Parent {
  void myName(String firstName) { // 오버라이딩
    System.out.println("저는 자식이고 이름은 " + lastName + firstName + "입니다.");
  }
}

오버라이딩의 조건

자손 클래스에서 오버라이딩하는 메서드는 조상 클래스의 메서드와

  • 이름이 같아야 한다.
  • 매개변수가 같아야 한다.

조상 클래스의 매서드를 자손 클래스에서 오버라이딩할 경우

  • 접근 제어자를 조상 클래스의 메서드보다 좁은 범위로 변경할 수 없다.
  • 예외는 조상 클래스의 메서드보다 많이 선언할 수 없다.
  • 인스턴스메서드를 static메서드로 또는 그 반대로 변경할 수 없다.

super 키워드

자손 클래스에서 조상 클래스로부터 상속받은 멤버를 참조하는데 사용하는 참조변수.

조상 클래스의 멤버와 자신의 멤버가 중복 정의되어있을때 구별하는 경우에 사용.

변수뿐만 아니라 오버라이딩한 경우에도 super를 사용.

예제

class Point {
  int x;
  int y;

  String getLocation() {
    return "x :" + x + ", y :" + y;
  }
}

class Point3D extends Point {
  int z;

  String getLocation() { // 오버라이딩
    // 조상의 메서드 호출 super
    return super.getLocation() + ", z :" + z;
  }
}

조상클래스의 메서드에 내용을 덧붙이는 작업을 하는 경우라면 super를 사용해서 조상클래스의 메서드를 포함시키는것이 좋다.

후에 조상클래스의 메서드를 수정할일이 생기더라도 자식클래스의 메서드를 같이 수정할 필요가 없기 때문이다.

super() 생성자

조상 클래스의 멤버변수는 조상의 생성자에 의해 초기화되어야한다.

super()를 통해 조상 클래스의 생성자를 호출한다.

Object 클래스를 제외한 모든 클래스의 생성자는 첫 줄에 반드시 자신의 다른 생성자 또는 조상의 생성자를 호출해야 한다.

그렇지 않을 경우 컴파일러가 자동으로 super();를 생성자의 첫줄에 삽입하게 된다.

예제

class Example {
    public static void main(String args[]) {
        Point3D p3 = new Point3D();
        System.out.println("p3.x = " + p3.x);
        System.out.println("p3.y = " + p3.y);
        System.out.println("p3.z = " + p3.z);
    }
}

class Point {
    // super();  
    // 생성자 첫줄에 다른 생성자를 호출하지 않았기 때문에 컴파일러가 super();를 삽입
    int x = 10;
    int y = 20;

    Point(int x, int y) {
        this.x = x;
        this.y = y;
    }
}

class Point3D extends Point {
    int z = 30;

    Point3D() {
        // Point3D(int x, int y, int z)를 호출한다.
        this(100, 200, 300);
    }

    Point3D(int x, int y, int z) {
        super(x, y); // Point(int x, int y)를 호출한다.
        this.z = z;
    }
}

final 키워드

변수에 사용시 : 변경할 수 없는 상수가 됨.

메서드에 사용시 : 오버라이딩 불가.

클래스에 사용시 : 자신을 확장하는 자손클래스 정의 불가.

Dynamic Method Dispatch

디스패치 : 어떤 메서드를 호출할 것인가를 결정하는 과정

정적 디스패치 : 컴파일타임에 어떤 메서드를 실행시킬지 결정

동적 디스패치 : 런타임시에 어떤 메서드를 실행시킬지 결정

예제

abstract class Card {
    abstract void printKind();
}

class Queen extends Card {
    public void printKind() {
        System.out.println("Queen");
    }
}

class King extends Card {
    public void printKind() {
        System.out.println("King");
    }
}

public class Example {
    public static void main(String[] args) {
        Card queen = new Queen();
        Card king = new King();

        queen.printKind();
        king.printKind();
    }
}

두 클래스 queen, king을 Card 타입 객체로 생성하였다.

그럼 과연 queen.printKind(), king.printKind() 수행시에 컴파일타임에서 각각의 객체는 어떤 클래스의 printKind를 실행하는것인지 알수있을까?

각 객체는 printKind를 실행하는것만 알 뿐, 어떤 클래스의 메서드인지는 모르는 상태이다.

Card queen = new Queen(); 으로 객체가 생성되면 객체는 자기자신의 객체 정보를 갖고있는 this 참조자를 가지는 것 처럼 receiver parameter라는 자기 객체의 정보를 담은 인자를 가지고 Card라는 타입에 저장한다.

따라서 런타임에 receiver parameter를 보고 어떤 객체인지 판단한 후 해당 클래스의 메서드를 호출한다.

instanceof 연산자

instanceof연산자는 객체가 어떤 클래스이고 어떤 클래스를 상속받았는지 확인하는데 사용되는 연산자이다.

object instanceof type

ArrayList와 List클래스를 예로들어 보자면 이들의 구조는 다음과 같다.

public class ArrayList<E> implements List {

}

public List {

}

이때 instanceof연산자를 통해 선언한 객체가 ArrayList인지, List로부터 상속받은 클래스의 객체인지를 확인할 수 있다.

ArrayList list = new ArrayList();

System.out.println(list instanceof ArrayList); // true
System.out.println(list instanceof List); // true

System.out.println(list instanceof Set); // false

ArrayList는 Set도 아니고, Set을 상속하지도 않기 때문에 false를 리턴한다.


참조
Java의 정석 $($남궁 성)
whiteship 자바 라이브 스터디

Categories:

Updated:

Leave a comment