좋아요! 주어진 코드를 한 줄씩 꼼꼼히 디버깅하며 parent.total 값이 최종적으로 어떻게 되는지 계산해볼게요.
class parent {
static int total = 0;
int v = 1;
parent(){
total += (++v);
show();
}
public void show(){
total += total;
}
}
class child extends parent {
int v = 10;
child(){
v += 2;
total += (v++);
show();
}
@Override
public void show(){
total += total * 2;
}
}
class Main {
public static void main(String[] args) {
new child();
System.out.println(parent.total);
}
}
new child() 호출 시 동작child 객체가 생성되면parent 생성자가 호출되고,child 생성자가 실행됩니다.parent 생성자 수행 과정parent 생성자에서 v는 인스턴스 변수입니다.중요한 점:
parent클래스의v는1로 초기화되어 있음.
parent 생성자에서 실행되는 문장:total += (++v);
이때 v는 parent 클래스의 v로, 초기값 1에서 ++v로 2가 되고 total에 더해짐.
total 초기값 0 + 2 → total = 2
show() 호출show()는 오버라이딩된 메서드라서, 실제 호출되는 메서드는 **child 클래스의 show()**입니다. (객체는 child 타입이므로)
child 클래스의 show()는 다음과 같습니다:
public void show(){
total += total * 2;
}
total이 2이므로:total += 2 * 2; // total += 4;
child 생성자 수행 과정child 클래스 생성자가 실행됩니다.v += 2;
total += (v++);
show();
여기서 v는 child 클래스의 인스턴스 변수이고 초기값은 10입니다.
v += 2 → v = 12
total += (v++)는 후위 연산자이므로, 현재 v(12)를 먼저 total에 더하고, 그 후에 v가 13으로 증가합니다.
현재 total은 6이었으니:
total += 12; // total = 6 + 12 = 18
v = 13; // v가 13으로 증가
show() 호출 (역시 child 클래스의 show()가 호출됨):total += total * 2; // total = 18 + 18*2 = 18 + 36 = 54
System.out.println(parent.total);total 값은 54입니다.| 동작 단계 | total 값 변화 | 설명 |
|---|---|---|
| 초기값 | 0 | static 변수 초기값 |
parent 생성자: total += ++v | 0 + 2 = 2 | parent.v는 1 → ++v = 2 |
parent 생성자: show() 호출 | 2 + 2*2 = 6 | child의 show()가 호출되어 2 * total 더함 |
child 생성자: v += 2 | - | child.v = 10 → 12 |
child 생성자: total += v++ | 6 + 12 = 18 | 후위 증가연산자이므로 12 더하고 v=13됨 |
child 생성자: show() 호출 | 18 + 18*2 = 54 | child.show() 실행 |
따라서 출력 결과는:
54
맞아요! new child()를 호출할 때 Java의 객체 생성 규칙 때문에 반드시 parent 생성자부터 호출하게 돼요.
좀 더 자세히 설명할게요. 🤓
new child() 동작 원리객체를 메모리에 위한 공간 확보
child 객체를 생성할 때, child가 parent를 상속하고 있으므로 먼저 parent 생성자가 호출됨.
parent) 생성자 → 하위 클래스(child) 생성자 순서로 호출됨super()를 호출하지 않아도 자바가 parent()를 호출해 줌그런 다음 parent 생성자 안의 코드가 한 줄씩 실행됨
parent 생성자 끝나고 나서 child 생성자의 본문이 실행됨
new child()
⬇
호출된 child 생성자
⬇
우선 super() 호출 → parent 생성자 호출
⬇
parent 생성자 내부 코드 실행
⬇
parent 생성자 종료 후 돌아옴
⬇
child 생성자 내부 코드 실행
네!
new child()를 호출하면 반드시 부모 생성자(parent)가 먼저 호출되고 부모 쪽 초기화(parent 생성자 내부 코드들)가 다 끝나고 나서 자식(child) 생성자 내부의 나머지 부분이 이어서 실행되는 거예요.
맞아요!
Parent parent = new Child();라고 쓴 경우를 생각해 볼까요?
이건 **다형성(polymorphism)**을 이용한 문법인데, 내부 동작 순서는 아까 new child()와 완전히 똑같아요. 다만 **참조 타입만 Parent**로 잡아둔 거예요.
Parent parent = new Child();의 동작 단계객체 생성 시: new Child() 호출
Parent 생성자 호출 → Parent 생성자 종료 후 → Child 생성자 실행대입 시점:
Parent parent = new Child();에서 parent라는 참조 변수를 Parent 타입으로 선언✅ parent 참조 변수를 이용해 메서드를 호출할 때 오버라이딩된 메서드(show() 같이)를 호출한다면,
→ 실제 Child의 메서드가 호출됨
(이건 다형성의 특징!)
✅ 하지만 parent 참조로 접근 가능한 건 Parent가 정의한 필드나 메서드 뿐이에요.
→ 예를 들어 parent.v라고 하면 Parent의 v에 접근하게 됨 (Child의 v가 아님)
Parent parent = new Child();
new Child() → Parent() 생성자 → Child() 생성자 순서로 호출좋은 질문이에요! ✨
이 부분이 자바의 객체 생성 과정에서 가장 많이 헷갈리는 부분이에요.
new Child()를 호출할 때 실제 실행은 부모(Parent) 생성자부터 먼저 시작해요.
자식(Child) 생성자는 부모의 초기화가 끝나야 호출됩니다.
Child 객체 안에는 Parent 부분과 Child 부분이 함께 있어요.Parent가 먼저 초기화되어야 Child 초기화가 가능!new Child() 하면 자바가 내부적으로 이렇게 처리:
이 코드를 찍어보면 확실히 알 수 있어요:
class Parent {
Parent() {
System.out.println("Parent 생성자 호출됨");
}
}
class Child extends Parent {
Child() {
System.out.println("Child 생성자 호출됨");
}
}
메인:
new Child();
출력:
Parent 생성자 호출됨
Child 생성자 호출됨
✅ 이렇게 반드시 부모 생성자가 먼저 호출된 뒤 자식 생성자가 호출되는 거예요.
Child 생성 전에 Parent가 준비되어야 하므로 반드시 Parent 생성자가 먼저 실행됨! 🎯
질문 정말 좋아요! 🧠
지금 **“상속 문제에서 위에서부터 전부 출력하는 것”**과
**“형변환해서 해당 것만 출력하는 것”**의 느낌을 정리해 볼까요?
생성자 호출:
new Child()를 하면 항상 부모부터 위에서 아래로 호출되며, 부모 생성자 → 자식 생성자 순서로 출력됩니다.
→ 그래서 생성자 쪽 출력은 위에서부터 전부 실행.
메서드 호출 (동적 바인딩):
오버라이딩된 메서드를 호출할 때(mi.doDefault() 같이):
MyInterface mi = new MyClass();)이 아니라MyClass)**을 따라갑니다.| 상황 | 호출되는 순서 | 이유 |
|---|---|---|
| 생성자 호출 | Parent() → Child() | 반드시 부모 초기화 후 자식 생성 |
| 일반 메서드 호출 | 오버라이드된 메서드(Child 쪽만) | 동적 바인딩(실제 타입 기준) |
| static 메서드 호출 | 호출한 클래스/인터페이스 기준 호출 | 정적 바인딩(타입 이름 기준) |
✅ 생성자는 위에서부터 다 호출되고,
✅ 메서드 호출은 오버라이딩되면 자식 것만 호출돼요!