상속은 "is-a" 방식의 재사용
상속을 사용하면 새 클래스를 기존 클래스 위에 쌓아 올릴 수 있습니다. 새 클래스, 즉 서브클래스는 슈퍼클래스의 필드와 메서드를 자동으로 물려받은 뒤, 필요한 것을 더하거나 바꿉니다. 한 타입이 다른 타입의 더 구체적인 종류일 때 상속을 사용합니다. Dog는 Animal이고, SavingsAccount는 BankAccount입니다.
이것은 extends 키워드로 작성합니다. 부모에서 public이거나 protected인 것은 모두 다시 작성하지 않아도 자식에서 사용할 수 있습니다.
Dog는 name이나 eat()을 한 번도 선언하지 않았지만 둘 다 가지고 있습니다. 바로 이것이 핵심입니다. 공유되는 동작이 한 곳에 모여 있는 것이죠.
super: 부모에 닿기
서브클래스 생성자는 부모가 먼저 초기화되도록 보장해야 합니다. 이를 super(...)로 하는데, 이는 부모의 생성자를 호출하며 서브클래스 생성자에서 반드시 첫 번째 문장이어야 합니다. 이를 빠뜨리면 자바는 부모의 인자 없는 생성자 호출을 조용히 삽입합니다. 그리고 부모에 그런 생성자가 없다면 코드는 컴파일되지 않습니다.
출력은 부모 생성자가 자식 본문보다 먼저 실행됨을 보여줍니다. 생성은 계층 구조의 위에서 아래로 흐릅니다.
메서드 오버라이딩
서브클래스는 상속받은 메서드를 같은 시그니처로 재정의하여 대체할 수 있습니다. 이것이 오버라이딩이며, 항상 @Override로 표시해야 합니다. 이 애너테이션은 필수는 아니지만, 여러분이 실제로 부모의 메서드와 일치했는지 컴파일러가 검증하게 만듭니다. toString() 대신 tostring() 같은 오타를 잡아 주는데, 그렇지 않으면 그 오타는 조용히 완전히 새로운 메서드를 만들어 버립니다.
배열의 타입이 Animal이더라도 각 요소는 자기 자신의 speak()을 실행합니다. 자바는 변수의 선언된 타입이 아니라 런타임의 실제 객체를 기준으로 메서드를 선택합니다. 이것이 다형성의 토대입니다.
super.method()로 부모 버전 호출하기
오버라이딩이 반드시 부모의 작업을 버려야 하는 것은 아닙니다. super.method()를 사용해 상속받은 버전을 실행한 뒤 거기에 덧붙이세요.
super.가 없으면 TimestampLogger.log 안에서 log를 호출하는 것은 자기 자신을 호출하여 무한히 재귀하게 됩니다. super.는 명시적으로 "부모의 버전"을 의미합니다.
상속되는 필드와 접근 권한
서브클래스는 부모의 public과 protected 멤버는 볼 수 있지만 private 멤버는 볼 수 없습니다. private 필드는 여전히 객체 안에 존재하며 부모 자신의 메서드는 그것에 접근할 수 있지만, 서브클래스는 직접 참조할 수 없습니다. 관련 없는 코드로부터는 멤버를 숨기면서 서브클래스에는 접근을 허용하고 싶을 때 접근 제어자인 protected를 사용하세요.
class Base {
private int secret; // 서브클래스에서 보이지 않음
protected int shared; // 서브클래스에서 보임
}
class Derived extends Base {
void demo() {
shared = 5; // OK
// secret = 5; // 컴파일 오류 - Base에 private
}
}
이것이 서브클래스 생성자가 종종 super(...)를 호출해야만 하는 이유이기도 합니다. 그것이 부모의 private 상태를 초기화하는 유일한 방법이기 때문입니다.
final로 상속 막기
때로는 클래스가 아예 상속되지 않아야 할 때가 있습니다. String이 바로 이 이유로 final입니다. 클래스를 final로 표시하면 서브클래싱이 금지됩니다. 메서드를 final로 표시하면 클래스 상속은 여전히 허용하면서 그 메서드의 오버라이딩을 금지합니다.
final class Constants { } // 서브클래싱할 수 없음
class Config {
final void load() { } // 서브클래스는 Config를 상속할 수 있지만
// load()는 오버라이딩할 수 없음
}
클래스의 동작이 프로그램 전체에서 보장되고 변하지 않아야 할 때 final을 사용하세요. 이는 기본값이 아니라 의도적인 "상속하지 마라"라는 신호입니다.
흔한 함정: "is-a"가 아니라면 컴포지션을 선호하라
상속은 코드를 재사용하기 때문에 매력적이지만, 자식을 부모에 단단히 결합시킵니다. 관계가 진정한 "is-a"가 아니라면, 가령 Engine이 필요할 뿐인 Car라면 Car extends Engine으로 만들지 마세요. 자동차는 엔진을 가지고 있는 것이지 엔진인 것이 아닙니다. 대신 필드(컴포지션)로 모델링하세요.
class Car {
private Engine engine = new Engine(); // Car는 Engine을 HAS-A(가지고 있다)
void start() { engine.ignite(); }
}
상속은 서브클래스가 정말로 슈퍼클래스의 특수화된 형태이고, 그 동작을 상속하면서 동시에 대체하고 싶을 때만 사용하세요.
다음: 인터페이스
extends를 통한 상속은 단 하나의 부모와 공유된 구현을 제공합니다. 하지만 클래스는 오직 한 클래스만 상속할 수 있습니다. 그렇다면 서로 관련 없는 클래스들에 공통 능력을 어떻게 부여할 수 있을까요? 바로 그것을 위해 인터페이스가 있습니다. 많은 클래스가 구현할 수 있는 계약이며, 다음 페이지의 주제입니다.
자주 묻는 질문
자바에서 상속이란 무엇인가요?
상속은 한 클래스(서브클래스)가 extends 키워드를 사용해 다른 클래스(슈퍼클래스)의 필드와 메서드를 재사용할 수 있게 해줍니다. 서브클래스는 부모의 public, protected 멤버를 자동으로 물려받으며, 새 멤버를 추가하거나 오버라이딩으로 상속받은 동작을 대체할 수 있습니다. 이는 "is-a(~이다)" 관계를 표현합니다. Dog는 Animal입니다.
자바에서 super 키워드는 무엇을 하나요?
super는 부모 클래스를 가리킵니다. 생성자 안의 super(...)는 부모의 생성자를 호출하며(그리고 반드시 첫 번째 문장이어야 합니다), super.method()는 여러분이 오버라이딩한 메서드의 부모 버전을 호출합니다. 이를 통해 서브클래스는 부모의 로직을 완전히 대체하는 대신 그 위에 쌓아 올릴 수 있습니다.
자바에서 오버라이딩과 오버로딩의 차이는 무엇인가요?
오버라이딩은 상속받은 메서드를 같은 시그니처로 서브클래스에서 재정의하여 동작을 바꾸는 것입니다. @Override로 표시하세요. 오버로딩은 같은 클래스 안에서 같은 이름이지만 다른 매개변수 목록을 가진 여러 메서드를 정의하는 것입니다. 오버라이딩은 상속과 런타임 디스패치에 관한 것이고, 오버로딩은 단지 이름이 같은 두 메서드일 뿐입니다.