본문 바로가기

JAVA

[JAVA] 상속과 super

💡 상속(Inheritance)

객체 지향 프로그래밍의 핵심 요소 중 하나다. 기존 클래스(부모 클래스)의 필드와 메서드를 새로운 클래스(자식 클래스)에서 재사용할 수 있게 해주는 개념이다. 말 그대로 기존 클래스의 속성과 기능을 새로운 클래스가 물려받는 것이다.

상속을 사용하려면 extends 키워드를 사용하며, 자바에서는 한 번에 하나의 클래스만 상속할 수 있다 (단일 상속). 상속은 클래스 간의 관계가 "is-a" 관계일 때 사용해야 한다. (예: "강아지는 동물이다(Dog is an Animal)")

  • 부모 클래스 (슈퍼 클래스): 자신의 필드와 메서드를 다른 클래스(자식 클래스)에게 상속해 주는 클래스
  • 자식 클래스 (서브 클래스): 부모 클래스로부터 필드와 메서드를 상속받아 사용하는 클래스

상속은 자식이 부모의 기능을 물려받는 방향으로 이루어진다. 그래서 자식 클래스는 부모 클래스에 접근할 수 있지만, 부모 클래스는 자신을 상속하는 자식 클래스의 존재를 알 수 없다. 이는 자식 클래스가 extends를 통해 부모를 명시적으로 선택하기 때문이다.

extends를 통해 부모 클래스의 멤버에 접근할 수 있는 구조적인 권한이 생기지만, 실제로 접근 가능한 범위는 부모 클래스의 접근 제어자(public, protected, default, private)에 따라 제한된다는 점도 기억해야 한다. 즉, 자식 클래스는 부모 클래스의 멤버를 무조건 자유롭게 사용할 수 있는 게 아니라, 접근 제어자의 규칙을 따르는 범위 내에서만 접근이 가능하다.


📌 단일 상속

자바에서는 다중 상속을 지원하지 않기 때문에, 한 번에 하나의 클래스만 상속할 수 있다. 물론 부모 클래스가 또 다른 부모 클래스를 가지는 것은 괜찮다. 다중 상속은 클래스 관계를 모호하게 만들고 클래스 계층 구조를 복잡하게 하며 **다이아몬드 문제(Diamond Problem)**와 같은 여러 문제점을 야기할 수 있어서 자바에서 클래스의 다중 상속을 허용하지 않는다.


🧠 상속과 메모리 구조

상속 관계의 객체를 생성하면, 힙(Heap) 영역에 부모 클래스와 자식 클래스의 인스턴스가 함께 생성된다. 내부적으로는 같은 참조값을 공유하지만, 부모 클래스와 자식 클래스는 각각 나뉘어 개별적으로 관리된다.

  • *호출자의 타입(참조 변수 타입)**을 기준으로 멤버(필드/메서드)를 찾기 시작한다. 현재 타입에서 찾지 못하면 상위 부모 클래스로 올라가며 호출 대상을 찾는다. 끝까지 찾아도 찾지 못하면 컴파일 오류가 발생한다. (필드는 정적 바인딩으로 컴파일 시점에 결정되고, 메서드는 동적 바인딩으로 런타임 시점에 결정된다는 개념과 연관된다.)

➕ 기능 추가와 클래스 확장

상속 관계 덕분에 새로운 자식 클래스를 추가해도 부모 클래스의 기능을 그대로 상속받기 때문에 코드 중복이 줄어들고, 확장이 매우 유용해진다.


🎯 상속과 메서드 오버라이딩

메서드 오버라이딩은 부모 타입의 기능을 자식의 입장에서 다르게 재정의하는 것을 말한다. 부모에게서 상속받은 메서드를 자식 클래스가 자신의 필요에 맞게 변경하는 것이다.

@Override 애노테이션을 메서드 재정의 시 사용하면, 만약 오버라이딩에 문제가 있을 경우 컴파일 오류를 내줘서 실수를 방지할 수 있다. @가 붙은 부분은 애노테이션이라고 하는데, 주석과 비슷하지만 프로그램이 읽을 수 있는 특별한 주석으로 생각하면 된다.


🔄 메서드 오버로딩 vs. 메서드 오버라이딩

  • 메서드 오버로딩: 메서드 이름은 같고 파라미터(매개변수)의 타입, 순서, 개수가 다른 메서드를 여러 개 정의하는 것을 말한다. (번역하면 '과적', 즉 같은 이름의 메서드를 여러 개 정의했다고 이해하면 된다.)
  • 메서드 오버라이딩: 하위 클래스에서 상위 클래스의 메서드를 재정의하는 과정이다.

📜 오버라이딩 조건

메서드를 오버라이딩하려면 다음 조건들을 만족해야 한다:

  • 메서드 이름이 같아야 한다.
  • *메서드 매개변수(파라미터)**의 타입, 순서, 개수가 같아야 한다.
  • 반환 타입이 같아야 하지만, 반환 타입이 하위 클래스 타입일 수도 있다 (공변 반환 타입).
  • 접근 제어자는 상위 클래스의 메서드보다 더 제한적이어서는 안 된다. 예를 들어, 부모 메서드가 protected라면 자식 메서드는 private나 default로 오버라이드할 수 없다.
  • 오버라이딩 메서드는 상위 클래스의 메서드보다 더 많은 **체크 예외(checked exception)**를 throw로 선언할 수 없다.
  • static, final, private 키워드가 붙은 메서드는 재정의할 수 없다.
    • static 메서드는 인스턴스 생성 없이 클래스 레벨에서 접근 가능하므로 오버라이딩 개념 자체가 적용되지 않는다. 오버라이딩은 인스턴스 메서드에만 해당된다.
    • final 메서드는 재정의가 금지된 메서드이므로 오버라이드할 수 없다.
    • private 메서드는 해당 클래스 내에서만 접근을 허용하는 접근 제어자이므로 다른 클래스에서 재정의하는 것은 불가능하다.
  • 생성자 오버라이딩은 할 수 없다. (생성자는 상속의 대상이 아니다.)

📌 final 키워드와 상속

final 키워드는 "최종적인", "변경할 수 없는"을 의미하며 상속과 관련하여 다음과 같은 제약이 있다.

  • final이 선언된 클래스는 확장이 불가능하다. 즉, 다른 클래스는 final로 선언된 클래스를 상속받을 수 없다.
  • final로 선언된 메서드는 오버라이드할 수 없다. 상속받은 서브 클래스에서 메서드 변경이 불가능하기 때문이다.

🔑 super 키워드

super는 부모 클래스를 참조하는 키워드다.

➡️ 필드/메서드 호출 시 super

부모와 자식의 필드명이 같거나, 메서드가 오버라이딩 되어 있으면 자식에서 부모의 필드나 메서드를 바로 호출할 수 없다. 이때 super 키워드를 사용하면 무조건 부모 클래스의 필드나 메서드를 참조하여 접근할 수 있다. (자신 클래스 내부에서 자기 자신의 필드나 메서드를 참조하는 this와 비슷하지만, super는 부모를 참조한다는 점이 다르다.)

만약 this로 찾는데 호출자가 자식 타입인 상황에서 해당 메서드나 필드를 못 찾으면 상위 부모 클래스로 올라가서 찾기는 한다.

➡️ 생성자 호출 시 super()

상속 관계의 인스턴스를 생성하면 메모리 내부에는 결국 자식과 부모 클래스가 모두 만들어지기 때문에 각각의 생성자 호출이 필요하다.

상속 관계를 사용하면 자식 클래스의 생성자에서 부모 클래스의 생성자를 반드시 호출해야 한다. (super())

  • super()는 부모가 기본 생성자인 경우에만 생략할 수 있다.
  • 생성자 초기화는 최상위 부모부터 이루어진다. 자식 생성자의 첫 줄에서 부모의 생성자를 호출해야 하기 때문이다.
  • 상속 관계의 생성자 호출은 결과적으로 부모에서 자식 순서로 실행된다. 따라서 부모의 생성자를 먼저 초기화하고, 자식의 데이터를 초기화한다.