다형성2

류한선·2025년 6월 21일

실기연습-2

목록 보기
20/95

좋아요! 🎯 이제 이 고난도의 문제한 줄 한 줄 디버깅하면서 출력값을 유도해 드릴게요.
이번 문제는 생성자 호출 순서, 오버라이딩 메서드 호출, 정적 메서드 바인딩, 필드 숨김, instanceof 검사까지 다루니, 집중해 주세요!


🧠 소스코드 다시 확인

class A {
    int x = 1;

    A() {
        System.out.println("A constructor");
        printX();
    }

    void printX() {
        System.out.println("A printX: " + x);
    }

    static void staticTest() {
        System.out.println("A staticTest");
    }
}

class B extends A {
    int x = 2;

    B() {
        System.out.println("B constructor");
    }

    @Override
    void printX() {
        System.out.println("B printX: " + this.x + ", super.x = " + super.x);
    }

    static void staticTest() {
        System.out.println("B staticTest");
    }
}

class C extends B {
    int x = 3;

    C() {
        System.out.println("C constructor");
    }

    @Override
    void printX() {
        System.out.println("C printX: " + this.x + ", super.x = " + super.x);
    }

    static void staticTest() {
        System.out.println("C staticTest");
    }
}

public class Main {
    public static void main(String[] args) {
        A a = new C();             // (1)
        a.printX();                // (2)
        a.staticTest();            // (3)

        B b = new C();             // (4)
        b.printX();                // (5)
        b.staticTest();            // (6)

        C c = new C();             // (7)
        c.printX();                // (8)
        c.staticTest();            // (9)

        if (a instanceof B) {      // (10)
            System.out.println("(a instanceof B) is true");
        }

        if (b instanceof C) {      // (11)
            System.out.println("(b instanceof C) is true");
        }
    }
}

🎯 트릭 정리

int x 필드는 각 클래스마다 별도로 존재this.xsuper.x 차이
✅ 생성자 안에서 printX() 호출 → 오버라이딩된 메서드 호출 (동적 바인딩)
✅ 정적 메서드 호출(staticTest()) → 정적 바인딩(참조 타입 기준 호출됨)
instanceof 검사 → 실제 객체 타입 기준 검사


🔍 한 줄 한 줄 디버깅


(1) A a = new C();

생성자 호출 순서

  1. new C() 호출 → 먼저 부모 생성자 호출
  2. C()B()A() 순서 호출
  3. A 생성자 내부에서 printX() 호출됨

A 생성자 안에서 printX() 호출

  • printX() 오버라이딩 → 실제 객체는 C → C의 printX() 호출
  • C의 필드들은 아직 초기화 전이므로 this.x = 0 상태
  • B의 필드도 초기화 전이므로 super.x = 0

필드 초기화 시점은 생성자 호출 전에 수행되므로 C의 x가 0 상태, B의 x가 0 상태.

출력

A constructor
C printX: 0, super.x = 0
B constructor
C constructor

(2) a.printX();

호출

  • a의 참조타입 = A, 실제객체 = C → 동적 바인딩 → C의 printX 호출
  • C의 필드 x = 3, super.x(B의 필드) = 2

출력

C printX: 3, super.x = 2

(3) a.staticTest();

호출

  • 정적 메서드 호출 → 정적 바인딩 → 참조 타입(A) 기준 호출

출력

A staticTest

(4) B b = new C();

호출 과정

  • 위 (1)과 동일하게 생성자 체인을 타며 호출

출력

A constructor
C printX: 0, super.x = 0
B constructor
C constructor

(5) b.printX();

호출

  • b의 참조타입 = B, 실제객체 = C → 동적 바인딩
  • C의 printX 호출 → C.x=3, super.x(B.x)=2

출력

C printX: 3, super.x = 2

(6) b.staticTest();

호출

  • 정적 메서드 호출 → 정적 바인딩 → 참조타입(B)의 staticTest 호출

출력

B staticTest

(7) C c = new C();

호출 과정

  • A → B → C 생성자 체인
  • 생성자 호출 안에서 printX() 호출됨

출력

A constructor
C printX: 0, super.x = 0
B constructor
C constructor

(8) c.printX();

호출

  • c의 참조타입=C → C의 printX 호출

출력

C printX: 3, super.x = 2

(9) c.staticTest();

호출

  • 정적 바인딩 → 참조타입(C) 기준 호출

출력

C staticTest

(10) if (a instanceof B)

검사

  • a의 실제 객체 = C → C는 B의 하위클래스 → true

출력

(a instanceof B) is true

(11) if (b instanceof C)

검사

  • b의 실제 객체 = C → C는 C의 인스턴스 → true

출력

(b instanceof C) is true

최종 출력 정리

A constructor
C printX: 0, super.x = 0
B constructor
C constructor
C printX: 3, super.x = 2
A staticTest
A constructor
C printX: 0, super.x = 0
B constructor
C constructor
C printX: 3, super.x = 2
B staticTest
A constructor
C printX: 0, super.x = 0
B constructor
C constructor
C printX: 3, super.x = 2
C staticTest
(a instanceof B) is true
(b instanceof C) is true

🎯 포인트 정리

✅ 생성자 호출 중 오버라이딩된 메서드를 호출하면 **필드 초기화 전 상태(0)**가 나올 수 있다
✅ 정적 메서드 호출은 참조 타입 기준
✅ 필드 은닉 → this.xsuper.x는 다른 값
instanceof 검사 → 실제 객체 타입 기준 검사


좋은 관찰이에요! 🎯
지금 혼동되는 부분을 확실히 정리해 드릴게요.


🧠 두 경우의 차이:

지금 본 문제(A a = new C();)와 이전 문제(Parent p = new Child(); Child c = new Child();)의 가장 큰 차이는:

이번 문제의 A 클래스 생성자 안에서 printX()를 호출한다는 점
이전 문제의 Parent 클래스 생성자 안에는 그런 호출이 없다는 점

이 부분 때문에 출력이 달라지는 거예요.


🧠 이전 문제 (Parent p = new Child() 예제)

이전 문제의 Parent, Child 클래스에는 생성자 안에서 오버라이딩 메서드를 호출하지 않았습니다.
예를 들어:

class Parent {
    Parent() {
        System.out.println("Parent constructor");
        // 별도로 print() 호출 없음
    }
}

→ 그래서 단순히

Parent constructor
Child constructor

만 출력되었어요.


🧠 이번 문제 (A a = new C())

이번 문제의 A 생성자는 이렇게 생겼습니다:

A() {
    System.out.println("A constructor"); // A생성자 호출
    printX();                             // ★★★ 오버라이딩된 메서드 호출
}

여기서 중요 포인트:
✅ 생성자 체인을 호출할 때, 아직 B, C의 필드 초기화가 끝나기 전에 printX() 호출이 발생
✅ 오버라이딩된 메서드(C.printX())가 호출되는데 C, B의 x 필드 초기화가 되지 않아서 0이 출력됨


🧠 생성자 호출 순서 정리 (new C()의 경우)

  1. new C() 호출 → 부모 생성자 호출(A())

  2. A() 생성자 안에서:

    • "A constructor" 출력
    • printX() 호출 → CprintX() 호출됨 (동적 바인딩)
    • 하지만 아직 B.x와 C.x 초기화 전 → 0 출력
  3. A() 끝나고 → B() 생성자 호출 → "B constructor"

  4. B() 끝나고 → C() 생성자 호출 → "C constructor"

이 때문에:

A constructor
C printX: 0, super.x = 0
B constructor
C constructor

가 출력된 거예요.


✅ 결론

  • 이번 문제의 경우 생성자 안에서 오버라이딩 메서드를 호출했기 때문에 초기화가 안 된 상태의 필드를 출력
  • 이전 문제의 경우 생성자 안에서 이런 호출이 없어서 단순히 생성자 호출 메시지만 출력

좋아요! 🎯 이제 이 문제를 한 줄 한 줄 디버깅하면서 출력값을 도출하고, 왜 그런 출력이 나오는지 자세히 설명해 드릴게요.
이번 문제는 정적 초기화, 오버라이딩, 인터페이스 디폴트 메서드 호출, 생성자 호출 트릭을 포함하고 있습니다.


🧠 소스코드 한 번 정리

interface MyInterface {
    default void show() {
        System.out.println("MyInterface show");
    }
    void doSomething();
}

class Base implements MyInterface {
    static {
        System.out.println("Base static block");
    }

    Base() {
        System.out.println("Base constructor");
        show();
        doSomething();
    }

    public void doSomething() {
        System.out.println("Base doSomething");
    }
}

class Derived extends Base {
    int num = 42;

    static {
        System.out.println("Derived static block");
    }

    Derived() {
        System.out.println("Derived constructor");
    }

    @Override
    public void doSomething() {
        System.out.println("Derived doSomething: num = " + num);
    }

    @Override
    public void show() {
        System.out.println("Derived show");
    }
}

class FinalTest extends Derived {
    final void cannotOverride() {
        System.out.println("FinalTest cannotOverride");
    }

    FinalTest() {
        System.out.println("FinalTest constructor");
    }
}

public class InterfaceTrickyTest {
    public static void main(String[] args) {
        Base b = new Derived();      // (1)
        b.show();                    // (2)
        b.doSomething();             // (3)
        if (b instanceof Derived) {  // (4)
            Derived d = (Derived) b;
            d.doSomething();
        }
        Base b2 = new FinalTest();   // (5)
        b2.doSomething();            // (6)
    }
}

🧠 트릭 포인트를 하나씩 이해하기

✅ 정적 초기화 블록(static {}): 클래스가 메모리에 최초 참조될 때 한 번 호출
✅ 생성자 호출 순서: new Derived() → 부모(Base()) → 자신의 생성자(Derived())
✅ 부모 생성자 안에서 show() 호출 → 동적 바인딩 → Derived.show() 호출
✅ 부모 생성자 안에서 doSomething() 호출 → 동적 바인딩 → Derived.doSomething() 호출
numDerived() 생성자 호출 전까지 0 (기본값)
final 메서드(cannotOverride()) → 오버라이딩 불가하지만 호출된 적 없음
instanceof 검사 참, 다운캐스팅 → Derived 메서드 호출
Base b2 = new FinalTest() → 정적 초기화 이미 끝났으므로 정적 블록 출력 안 함


🔍 한 줄 한 줄 디버깅 & 출력값

(1) Base b = new Derived();

클래스 초기화

  1. Base 클래스가 아직 로딩 안 됨 → Base static block 출력
  2. Derived 클래스가 아직 로딩 안 됨 → Derived static block 출력

생성자 호출

  1. Base() 생성자 호출:

    • "Base constructor" 출력
    • show() 호출 → 동적 바인딩 → Derived.show() 호출 → 출력 "Derived show"
    • doSomething() 호출 → 동적 바인딩 → Derived.doSomething() 호출
      → 아직 num 초기화 전이므로 num = 0 → 출력 "Derived doSomething: num = 0"
  2. Derived() 생성자 호출 → "Derived constructor" 출력
    → (여기서 num=42 초기화 완료)


(2) b.show();

  • b 참조 타입 = Base, 실제 객체 = Derived
  • 오버라이딩된 Derived.show() 호출 → 출력 "Derived show"

(3) b.doSomething();

  • 오버라이딩된 Derived.doSomething() 호출 → num=42 → 출력 "Derived doSomething: num = 42"

(4) if (b instanceof Derived)

  • 참 → 다운캐스팅 Derived d = (Derived) b;
  • d.doSomething() 호출 → Derived.doSomething() → 출력 "Derived doSomething: num = 42"

(5) Base b2 = new FinalTest();

정적 초기화?

  • BaseDerived 이미 로딩됨 → 정적 블록 없음

  • FinalTest 정적 블록 없음

  • 생성자 호출 체인:

    1. Base() 호출 → 출력 "Base constructor"

      • show()Derived.show() 호출 → 출력 "Derived show"
      • doSomething()Derived.doSomething() 호출 → num 초기화 전 → 출력 "Derived doSomething: num = 0"
    2. Derived() 호출 → 출력 "Derived constructor"

    3. FinalTest() 호출 → 출력 "FinalTest constructor"


(6) b2.doSomething();

  • 오버라이딩된 Derived.doSomething() 호출 → num=42 → 출력 "Derived doSomething: num = 42"

📝 최종 출력 정리

순서대로 정리해 보겠습니다:

Base static block
Derived static block
Base constructor
Derived show
Derived doSomething: num = 0
Derived constructor
Derived show
Derived doSomething: num = 42
Derived doSomething: num = 42
Base constructor
Derived show
Derived doSomething: num = 0
Derived constructor
FinalTest constructor
Derived doSomething: num = 42

🎯 포인트 정리

✅ 정적 블록 → 클래스 최초 참조 시 한 번만 호출
✅ 생성자 호출 중 오버라이딩된 메서드 호출 → 초기화 안 된 필드(num=0)
✅ 오버라이딩 메서드 → 동적 바인딩 → 실제 객체 기준 호출
instanceof 검사 → true → 다운캐스팅 가능
final 메서드 오버라이딩 불가 → 호출된 적 없음


0개의 댓글