좋아요! 🎯 이제 이 고난도의 문제를 한 줄 한 줄 디버깅하면서 출력값을 유도해 드릴게요.
이번 문제는 생성자 호출 순서, 오버라이딩 메서드 호출, 정적 메서드 바인딩, 필드 숨김, 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.x와 super.x 차이
✅ 생성자 안에서 printX() 호출 → 오버라이딩된 메서드 호출 (동적 바인딩)
✅ 정적 메서드 호출(staticTest()) → 정적 바인딩(참조 타입 기준 호출됨)
✅ instanceof 검사 → 실제 객체 타입 기준 검사
A a = new C();new C() 호출 → 먼저 부모 생성자 호출C() → B() → A() 순서 호출printX() 호출됨printX() 호출printX() 오버라이딩 → 실제 객체는 C → C의 printX() 호출this.x = 0 상태super.x = 0필드 초기화 시점은 생성자 호출 전에 수행되므로 C의 x가 0 상태, B의 x가 0 상태.
A constructor
C printX: 0, super.x = 0
B constructor
C constructor
a.printX();C printX: 3, super.x = 2
a.staticTest();A staticTest
B b = new C();A constructor
C printX: 0, super.x = 0
B constructor
C constructor
b.printX();C printX: 3, super.x = 2
b.staticTest();B staticTest
C c = new C();A constructor
C printX: 0, super.x = 0
B constructor
C constructor
c.printX();C printX: 3, super.x = 2
c.staticTest();C staticTest
if (a instanceof B)(a instanceof B) is true
if (b instanceof C)(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.x와 super.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()의 경우)new C() 호출 → 부모 생성자 호출(A())
A() 생성자 안에서:
"A constructor" 출력printX() 호출 → C의 printX() 호출됨 (동적 바인딩)0 출력A() 끝나고 → B() 생성자 호출 → "B constructor"
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() 호출
✅ num은 Derived() 생성자 호출 전까지 0 (기본값)
✅ final 메서드(cannotOverride()) → 오버라이딩 불가하지만 호출된 적 없음
✅ instanceof 검사 참, 다운캐스팅 → Derived 메서드 호출
✅ Base b2 = new FinalTest() → 정적 초기화 이미 끝났으므로 정적 블록 출력 안 함
Base b = new Derived();Base 클래스가 아직 로딩 안 됨 → Base static block 출력Derived 클래스가 아직 로딩 안 됨 → Derived static block 출력Base() 생성자 호출:
"Base constructor" 출력show() 호출 → 동적 바인딩 → Derived.show() 호출 → 출력 "Derived show"doSomething() 호출 → 동적 바인딩 → Derived.doSomething() 호출num 초기화 전이므로 num = 0 → 출력 "Derived doSomething: num = 0"Derived() 생성자 호출 → "Derived constructor" 출력
→ (여기서 num=42 초기화 완료)
b.show();b 참조 타입 = Base, 실제 객체 = DerivedDerived.show() 호출 → 출력 "Derived show"b.doSomething();Derived.doSomething() 호출 → num=42 → 출력 "Derived doSomething: num = 42"if (b instanceof Derived)Derived d = (Derived) b;d.doSomething() 호출 → Derived.doSomething() → 출력 "Derived doSomething: num = 42"Base b2 = new FinalTest();Base와 Derived 이미 로딩됨 → 정적 블록 없음
FinalTest 정적 블록 없음
생성자 호출 체인:
Base() 호출 → 출력 "Base constructor"
show() → Derived.show() 호출 → 출력 "Derived show"doSomething() → Derived.doSomething() 호출 → num 초기화 전 → 출력 "Derived doSomething: num = 0"Derived() 호출 → 출력 "Derived constructor"
FinalTest() 호출 → 출력 "FinalTest constructor"
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 메서드 오버라이딩 불가 → 호출된 적 없음