Java Inheritance - 놓치기 쉬운 개념들

이강현·2025년 4월 4일

상속 범위

  • 생성자초기화 블럭은 상속되지 않습니다. 멤버만 상속됩니다.
  • 접근 제어자가 private 또는 default 인 멤버들은 상속되지 않는다기보다 상속은 받지만 자손 클래스로부터 접근이 제한되는 것입니다.
  • 모든 멤버를 복사해서 가져오지만 접근 제어자에 따라 제한 여부가 결정됩니다.
  • 자손 클래스의 인스턴스를 생성하면 조상 클래스의 멤버와 자손 클래스의 멤버가 합쳐진 하나의 인스턴스로 생성됩니다. 참조값도 따로 생기지 않습니다.



Composite & Aggregation

클래스간의 관계를 맺어주는 방법으로 상속보다 더 유연한 포함을 자주 사용합니다.
포함 관계를 설명하는 두가지 개념이 있습니다.
1. Composite(합성) : ex) 자동차-엔진, 집-방
2. Aggregation(집합) : ex) 학과-학생, 도서관-책

특징Composite (합성)Aggregation (집합)
연관 관계강함 (Strong)약함 (Weak)
존재 의존성부분 객체는 전체 객체에 의존적 (전체 소멸 시 부분 소멸)부분 객체는 전체 객체와 독립적 (전체 소멸해도 부분 존재)
소유권배타적 소유 (Exclusive)공유 가능 (Shared)
생명주기 관리전체 객체가 부분 객체의 생성 및 소멸 관리부분 객체의 생성 및 소멸은 독립적일 수 있음



단일 상속

Java는 다중 상속을 의도적으로 막았습니다.
단일 상속만 허용하는 이유는 다음과 같습니다.

  • 클래스 관계단순하고 명확하게
  • 멤버의 이름이 겹치는 경우를 방지



변수 구별

  • 이름이 같은 변수라도 this, super를 통해 구별할 수 있습니다.
  • super는 한 번만 사용 가능합니다.
  • 부모보다 상위 클래스의 인스턴스 변수는 up-casting을 통해 접근 가능합니다.
    • 이 방법이 필요한 경우, 클래스 재설계를 고려해야 합니다.
public class InheritanceTestMain {
    public static void main(String[] args) {
        GrandChild grandChild = new GrandChild();
        grandChild.myMethod(1);
    }
}

class Parent {
    int v=4;
}

class Child extends Parent{
    int v=3;
}

class GrandChild extends Child{
    int v=2;

    void myMethod(int v) {
        System.out.println("v = " + v); // local var v
        System.out.println("this.v = " + this.v); // GrandChild instance var v
        System.out.println("super.v = " + super.v); // Child instance var v
        System.out.println("((Parent) this).v = " + ((Parent) this).v); // Parent instance var v
    }
}



this(), super()

  • 자식 클래스의 모든 생성자에서 첫 줄은 반드시 자신 혹은 부모 클래스의 생성자를 호출 해야 합니다.
    • 자식 클래스는 부모 클래스의 모든 멤버를 상속받고
      생성자가 호출되는 시점부터 그 멤버들을 사용할 수 있기 때문에 반드시 초기화가 필요합니다.

  • 부모 클래스에 기본 생성자(매개변수가 없는 생성자)가 있는 경우
    자식 클래스의 생성자들은 부모 클래스의 생성자(super())를 호출 하지 않으면
    컴파일러가 자동으로 부모 클래스의 기본 생성자를 추가해줍니다.
    • 모든 클래스의 공통 조상인 Object 의 생성자는 언제나 호출됩니다.



Overriding & Up-Casting

자식 클래스가 부모 클래스의 메서드를 오버라이딩 했다면
자식 클래스의 인스턴스에서는

  • 업 캐스팅을 하더라도 오버라이딩 하기 전의 부모 메서드를 호출 할 수 없습니다.
  • 자식 클래스가 오버라이딩 하기 전 메서드를 호출하기 위해서는
    super 를 사용하는 방법 뿐입니다.
public class SuperTest {
    public static void main(String[] args) {
        ParentClass child = new ChildClass(); //up-casting
        child.myMethod(); // -> "ChildClass.myMethod"
        // there is no way to call ParentClass.myMethod()
        if (child instanceof ChildClass c) {
            c.callParentMethod(); // -> "ParentClass.myMethod" 
        }
    }
}

class ParentClass {
    void myMethod() {
        System.out.println("ParentClass.myMethod");
    }
}

class ChildClass extends ParentClass{
    @Override
    void myMethod() {
        System.out.println("ChildClass.myMethod");
    }

    void callParentMethod() {
        super.myMethod(); // only way
    }
}



instanceof

instanceof 연산은 다음과 같은 경우에 컴파일러 에러가 발생합니다.
1. 좌항 피연산자참조값 또는 null 이 아닌 경우
2. 우항 피연산자제네릭 타입인 경우(와일드카드 타입은 허용)
3. 좌항의 피연산자를 우항의 피연산자로 형변환 불가능한(상속 관계가 없는) 경우

public class InstanceofTest {
    public static void main(String[] args) {
        FireEngine fe = new FireEngine();

        boolean b1 = fe instanceof FireEngine;
        boolean b2 = fe instanceof Movable;
        boolean b3 = fe instanceof Object;
        boolean b4 = fe instanceof Car;
//        boolean b5 = fe instanceof Ambulance; // <- compiler error
    }
}

class Car {}
interface Movable {}
class FireEngine extends Car implements Movable {}
class Ambulance extends Car {}



null

null 의 타입이 무엇인지 java 에서 명시하고 있지는 않지만
모든 참조 변수에 대입 가능하다는 점에서 최저 자손이라고 생각해 볼 수 있습니다.

  • 모든 클래스의 최고 조상Object 타입의 참조 변수는 대입 연산자우항에 어떤 참조 타입의 값이 오더라도 자동 형변환되어 할당될 수 있습니다.
  • 같은 관점에서 null은 대입 연산자의 좌항에 어떤 참조 변수 타입이 오더라도 자동 형변환되니 모든 클래스의 최저 자손이라고 할 수 있습니다.
Object o1 = new MyClass();
Object o2 = new String();
Object o3 = null;

MyClass myClass = null;
String string = null;
Object object = null;



import

  • Java 의 import 문은 런타임 성능에 직접적인 영향을 주지 않습니다.
  • 클래스 로딩과는 별개의 작업입니다.
  • import 문의 주요 목적은 코드 내에서 클래스나 인터페이스의 정규화된 이름을 매번 쓰는 번거로움을 덜어주고 코드의 가독성을 높이는 것입니다.
profile
백엔드 개발자 지망생입니다.

0개의 댓글