# 7. 상속

# 7.1 상속 개념

  • 상속 : 부모가 자식에게 물려주는 행위
  • 객체지향 프로그램에서 상속 : 부모 클래스의 필드와 메소드를 자식 클래스에게 물려줌
  • 상속은 이미 잘 개발된 클래스를 재사용 해서 새로운 클래스를 만들기 때문에 중복되는 코드를 줄여 개발 시간을 단축시킨다
  • 상속은 클래스 수정을 최소화 할 수 있다. 부모 클래스를 수정하면 모든 자식 클래스에 수정 효과를 가져온다.

# 7.2 클래스 상속

  • 프로그램에서는 자식이 부모를 선택

      public class 자식클래스 extends 부모클래스 {}
    
  • 다른 언어와 달리 자바는 다중 상속을 허용하지 않는다. 즉 여러개의 부모 클래스를 상속할 수 없다. 따라서 extends 뒤에는 단 하나의 부모클래스만이 와야한다.

# 7.3 부모 생성자 호출

  • 자바에서 자식 객체를 생성하면 부모 객체가 먼저 생성된 다음에 자식 객체가 생성된다.

자식클래스 변수 = new 자식클래스();
  • 자식 객체만 생성되는 것처럼 보이지만, 사실은 부모 객체가 먼저 생성되고 그 다음에 자식 객체가 생성된다.
  • 모든 객체는 생성자를 호출해야만 생성된다. 부모 객체도 예외는 아님. 부모 객체의 생성자는 자식 생성자의 맨 첫줄에 숨겨져 있는 super() 에 의해 호출된다.

// 자식 생성자 선언
public 자식클래스(...){
    super(); // 생략가능
    ...
}
  • super() 는 컴파일 과정에서 자동 추가. 부모의 기본 생성자를 호출한다.
  • 부모 클래스에 기본 생성자가 없다면 자식 생성자 선언에서 컴파일 에러가 발생
  • 부모 클래스에 기본 생성자가 없고 매개변수를 갖는 생성자만 있다면 개발자는 다음과 같이 super(매개값, ...) 코드를 직접 넣어야 한다.
  • super(매개값,...) : 매개값의 타입과 개수가 일치하는 부모 생성자를 호출한다.

// 자식 생성자 선언
public 자식클래스(...) {
    super(매개값, ...); // 반드시 작성해야함
}

# 7.4 메소드 재정의

# 메소드 오버라이딩(overriding)

  • 상속된 메소드를 자식 클래스에서 재정의 해서 사용.
  • 메소드가 오버라이딩되었다면 해당 부모 메소드는 숨겨지고, 자식 메소드가 우선적으로 사용된다.
  • 메소드 오버라이딩 규칙
    • 부모 메소드의 선언부(리턴타입, 메소드 이름, 매개변수) 와 동일해야 한다.
    • 접근 제한을 더 강하게 오버라이딩할 수 없다(public -> private으로 변경불가)
    • 새로운 예외를 throws 할 수 없다
  • @Override : 컴파일 단계에서 정확히 오버라이딩이 되었는지 체크하고, 문제가 있다면 컴파일 에러를 출력한다

# 부모 메소드 호출

  • 메소드를 재정의하면 부모 메소드는 숨겨지고 자식 메소드만 사용되기 때문에 비록 부모 메소드의 일부만 변경된다 하더라도 중복된 내용을 자식 메소드도 가지고 있어야 한다.
  • 자식 메소드와 부모 메소드의 공동 작업 처리 기법을 이용하면 매우 쉽게 해결된다.
  • super.method() : 자식 메소드 내에서 부모 메소드 호출
class Parent {
    public void method(){
        // 작업처리 1
    }
}
class Child extends Parent {
    @Override
    void method(){
        super.method();
        // 작업처리 2
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
  • super.method() 의 위치는 작업처리2 전후에 어디든지 올 수 있다. 우선 처리가 되어야 할 내용을 먼저 작성하면 된다.
  • 이 방법은 부모 메소드를 재사용함으로써 자식 메소드의 중복 작업 내용을 없애는 효과를 가져온다.

# 7.5 final 클래스와 final 메소드

# final 클래스

  • 클래스를 선언할 때 final 키워드를 class 앞에 붙이면 최종적인 클래스이므로 더 이상 상속할 수 없는 클래스가 된다.
  • final 클래스는 부모 클래스가 될 수 없어 자식 클래스를 만들 수 없다.
  • 대표적인 예 : String 클래스

  public final class 클래스 [...]

# final 메소드

  • 메소드를 선언할 때 final 키워드를 붙이면 이 메소드는 최종적인 메소드이므로 오버라이딩할 수 없는 메소드가 된다.
  • 부모 클래스를 상속해서 자식 클래스를 선언할때, 부모 클래스에 선언된 final 메소드는 자식 클래스에서 재정의 할 수 없다.

public final 리턴타입 메소드(매개변수,...){...}

# 7.6 protected 접근 제한자

접근제한자 제한대상 제한범위
protected 필드, 생성자, 메소드 같은 패키지이거나, 자식 객체만 사용 가능
  • protected는 같은 패키지에서는 default 처럼 접근이 가능하나, 다른 패키지에서는 자식 클래스만 접근을 허용한다. protected는 필드와 생성자 그리고 메소드 선언에 사용될 수 있다.
  • 자식 클래스는 부모의 protected 필드, 생성자, 메소드에 접근 가능하다. 단 new 연산자를 사용해서 생성자를 직접 호출할 수는 없고, 자식 생성자에서 super()로 부모 생성자를 호출할 수 있다.

# 7.7 타입변환

  • 클래스에서의 타입변환은 상속관계에 있는 클래스 사이에서 발생한다.

# 자동 타입 변환

  • 자동적으로 타입 변환이 일어나는것.

부모타입 변수 = 자식타입객체;    
// 자동 타입 변환
  • 자식은 부모의 특징과 기능을 상속받기 때문에 부모와 동일하게 취급될 수 있다.
Cat cat = new Cat();
Animal animal = cat; // 자동 타입 변환
1
2
  • 바로 위의 부모가 아니더라고 상속 계층에서 상위 타입이라면 자동 타입 변환이 일어날 수 있다.
  • 부모 타입으로 자동 타입 변환된 이후에는 부모 클래스에 선언된 필드와 메소드만 접근이 가능하다.
  • 변수는 자식 객체를 참조하지만 변수로 접근 가능한 멤버는 부모 클래스 멤버로 한정
  • 그러나 자식 클래스에서 오버라이딩된 메소드가 있다면 부모 메소드 대신 오버라이딩된 메소드가 호출된다.(다형성)
class Parent {
    void method1() {...}
    void method2() {...}
}
class Child extends Parent{
    void method2() {...} // 오버라이딩
    void method3() {...}
}
class ChildExample{
    public static void main(String[] args) {
        Child child = new Child();
        Parent parent = child;
        parent.method1(); // Parent method1 호출
        parent.method2(); // Child method2 호출
        parent.method3(); // 호출불가능
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# 강제 타입 변환

  • 자식 타입은 부모 타입으로 자동 변환되지만, 반대로 부모 타입은 자식 타입으로 자동 변환되지 않는다.
  • 대신 캐스팅연산자로 강제 타입 변환(casting) 을 할 수 있다.

자식타입 변수 = (자식타입) 부모타입객체;
  • 부모 타입 객체를 자식 타입으로 무조건 강제 변환할 수 있는 것은 아니다. 자식 객체가 부모 타입으로 자동 변환된 후 다시 자식 타입으로 변환할때 강제 타입 변환을 사용할 수 있다.

Parent parent = new Child(); // 자동 타입 변환
Child child = (Child) parent; // 강제 타입 변환
  • 자식 객체가 부모 타입으로 자동 변환 하면 부모 타입에 선언된 필드와 메소드만 사용 가능하다는 제약 사항이 따른다.
  • 만약 자식 타입에 선언된 필드와 메소드를 꼭 사용해야 한다면 강제 타입 변환을 해서 다시 자식 타입으로 변환해야 한다.

# 7.8 다형성

  • 사용 방법은 동일하지만 실행 결과가 다르게 나오는 성질
  • 객체 사용 방법이 동일하다는 것은 동일한 메소드를 가지고 있다는 뜻

자동 타입 변환 + 메소드 오버라이딩 -> 다형성

# 필드 다형성

  • 필드 타입은 동일하지만(사용 방법은 동일하지만), 대입되는 객체가 달라져서 실행 결과가 다양하게 나올 수 있는 것을 말한다.

Car myCar = new Car();
myCar.tire = new HankookTire();
myCar.run();
myCar.tire = new KumhoTire();
myCar.run();
  • 어떤 타이어를 장착했는지에 따라 run() 메소드의 실행 결과는 달라지게 된다. 이것이 필드의 다형성

# 매개변수 다형성

  • 메소드가 클래스 타입의 매개변수를 가지고 있을 경우, 호출할때 동일한 타입의 객체를 제공하는 것이 정석이지만, 자식 객체를 제공할 수도 있다.

public class Driver {
    public void drive(Vehicle vehicle) {
        vehicle.run();
    }
}

Driver driver = new Driver();
Bus bus = new Bus(); // vehicle 의 자식 객체
driver.drive(bus); // Bus 객체의 run() 호출
  • drive() 메소드는 매개변수 vehicle이 참조하는 객체의 run() 메소드를 호출하는데, 자식 객체가 run() 메소드를 재정의하고 있다면 재정의된 run() 메소드가 호출된다.
  • 매개변수의 다형성 : 어떤 자식 객체가 제공되느냐에 따라서 drive() 의 실행 결과는 달라진다.

# 7.9 객체 타입 확인

  • instanceof : 매개변수의 다형성에서 실제로 어떤 객체가 매개값으로 제공되었는지 확인하는 방법

boolean result = 객체 instanceof 타입;
  • 좌항의 객체가 우항의 타입이면 true 그렇지 않으면 false;

public void method(Parent parent) {
    if(parent instanceof Child) {
        Child child = (Child) parent;
    }
}
  • Java 12 부터는 instanceof 연산의 결과가 true일 경우, 우측 타입 변수를 사용할 수 있기 때문에 강제 타입 변환이 필요 없다

public void method(Parent parent) {
    if(parent instanceof Child) {
        // child 변수 사용
    }
}

# 7.10 추상 클래스

  • 추상(abstract) : 실체 간에 공통되는 특성을 추출한 것

# 추상클래스란?

  • 객체를 생성할 수 있는 클래스를 실체클래스라고 한다면, 이 클래스들의 공통적인 필드나 메소드를 추출해서 선언한 클래스를 추상클래스 라고 한다.
  • 추상클래스는 실체 클래스의 부모 역할을 한다. 따라서 실체 클래스는 추상 클래스를 상속해서 공통적인 필드나 메소드를 물려받을 수 있다.
  • 추상 클래스는 새로운 실체 클래스를 만들기 위한 부모 클래스로만 사용된다. 즉 extends 뒤에만 올 수 있다.

# 추상클래스 선언

  • 클래스 선언에 abstract 키워드를 붙이면 추상 클래스 선언이 된다.
  • 추상 클래스는 new 연산자를 이용해서 객체를 직접 만들지 못하고 상속을 통해 자식 클래스만 만들 수 있다.

public abstract class 클래스명 { // 필드 // 생성자 // 메소드 }
  • 추상클래스도 필드, 메소드를 선언할 수 있다.
  • 자식 객체가 생성될때 super()로 추상클래스의 생성자가 호출되기 때문에 생성자도 반드시 있어야 한다.

# 추상 메서드와 재정의

  • 자식 클래스들이 가지고 있는 공통 메소드를 뽑아내어 추상 클래스로 작성할때, 메소드 선언부(리턴타입, 메소드명, 매개변수) 만 동일하고 실행 내용은 자식 클래스마다 달라야 하는 경우가 많다.
  • 이런 경우를 위해 추상 클래스는 추상 메소드를 선언할 수 있다. 일반 메소드 선언과의 차이점은 abstract 키워드가 붙고, 메소드 실행 내용인 중괄호 {} 가 없다.

abstract 리턴타입 메소드명(매개변수,...);
  • 추상 메소드는 자식 클래스의 공통 메소드라는 것만 정의할 뿐, 실행 내용을 가지지 않는다.
  • 추상 메소드는 자식 클래스에서 반드시 재정의 (오버라이딩) 해서 실행 내용을 채워야 한다.

# 7.11 봉인된 클래스

  • 기본적으로 final 클래스를 제외한 모든 클래스는 부모 클래스가 될 수 있다.
  • java15 부터 무분별한 자식 클래스 생성을 방지하기 위해 봉인된(sealed)클래스가 도입되었다.

public sealed class Person permits Employee, Manager {...}
  • Person 의 자식클래스는 Employee 와 Manager만 가능하고, 그 이외는 자식클래스가 될 수 없도록 Person을 봉인된 클래스로 선언
  • 봉인된 클래스 Person을 상속하는 Employee 와 Manager는 final 또는 non-sealed 키워드로 다음과 같이 선언하거나, sealed 키워드를 사용해서 또 다른 봉인 클래스로 선언해야 한다.

public final class Employee extends Person {...}
public non-sealed class Manager extends Person {...}
  • final : 더 이상 상속할 수 없다
  • non-sealed : 봉인을 해제
  • Employee는 더이상 자식 클래스를 만들 수 없지만, Manager는 자식 클래스를 만들 수 있다.