상속2

류한선·2025년 6월 26일

실기연습-2

목록 보기
44/95

좋아요! 주어진 코드를 한 줄씩 꼼꼼히 디버깅하며 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);
    }
}

Step by Step 실행 과정

1. new child() 호출 시 동작

  • child 객체가 생성되면
  • 먼저 부모 클래스인 parent 생성자가 호출되고,
  • 그 후 child 생성자가 실행됩니다.

2. 부모 클래스 parent 생성자 수행 과정

  • parent 생성자에서 v는 인스턴스 변수입니다.

중요한 점: parent 클래스의 v1로 초기화되어 있음.

  • parent 생성자에서 실행되는 문장:
total += (++v);
  • 이때 vparent 클래스의 v로, 초기값 1에서 ++v로 2가 되고 total에 더해짐.

  • total 초기값 0 + 2 → total = 2


3. 부모 생성자에서 show() 호출

  • show()는 오버라이딩된 메서드라서, 실제 호출되는 메서드는 **child 클래스의 show()**입니다. (객체는 child 타입이므로)

  • child 클래스의 show()는 다음과 같습니다:

public void show(){
    total += total * 2;
}
  • 현재 total이 2이므로:
total += 2 * 2;  // total += 4;
  • 즉, total = 2 + 4 = 6

4. child 생성자 수행 과정

  • child 클래스 생성자가 실행됩니다.
v += 2;
total += (v++);
show();
  • 여기서 vchild 클래스의 인스턴스 변수이고 초기값은 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

5. System.out.println(parent.total);

  • 최종 total 값은 54입니다.

최종 정리

동작 단계total 값 변화설명
초기값0static 변수 초기값
parent 생성자: total += ++v0 + 2 = 2parent.v는 1 → ++v = 2
parent 생성자: show() 호출2 + 2*2 = 6child의 show()가 호출되어 2 * total 더함
child 생성자: v += 2-child.v = 10 → 12
child 생성자: total += v++6 + 12 = 18후위 증가연산자이므로 12 더하고 v=13됨
child 생성자: show() 호출18 + 18*2 = 54child.show() 실행

따라서 출력 결과는:

54

맞아요! new child()를 호출할 때 Java의 객체 생성 규칙 때문에 반드시 parent 생성자부터 호출하게 돼요.

좀 더 자세히 설명할게요. 🤓


🎯 new child() 동작 원리

  1. 객체를 메모리에 위한 공간 확보

  2. child 객체를 생성할 때, childparent를 상속하고 있으므로 먼저 parent 생성자가 호출됨.

    • 상속 구조에서는 반드시 상위 클래스(parent) 생성자 → 하위 클래스(child) 생성자 순서로 호출됨
    • 명시적으로 super()를 호출하지 않아도 자바가 parent()를 호출해 줌
  3. 그런 다음 parent 생성자 안의 코드가 한 줄씩 실행됨

  4. 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();의 동작 단계

  1. 객체 생성 시: new Child() 호출

    • Child 객체를 생성하기 위해 Parent 생성자 호출 → Parent 생성자 종료 후 → Child 생성자 실행
      (여기까진 아까와 동일!)
  2. 대입 시점:

    • Parent parent = new Child();에서 parent라는 참조 변수를 Parent 타입으로 선언
    • 하지만, 이 참조 변수는 실제 내부적으로 Child 객체의 주소를 가리키고 있음.

📌 이때 중요한 점!

parent 참조 변수를 이용해 메서드를 호출할 때 오버라이딩된 메서드(show() 같이)를 호출한다면,
실제 Child의 메서드가 호출됨
(이건 다형성의 특징!)

✅ 하지만 parent 참조로 접근 가능한 건 Parent가 정의한 필드나 메서드 뿐이에요.
→ 예를 들어 parent.v라고 하면 Parent의 v에 접근하게 됨 (Child의 v가 아님)


🧠 정리

Parent parent = new Child();
  1. 내부적으로 new Child()Parent() 생성자 → Child() 생성자 순서로 호출
  2. 참조 타입은 Parent지만 실제 객체는 Child
  3. 오버라이딩된 메서드는 Child의 것 호출됨
  4. 필드나 메서드 참조 가능 범위는 Parent 타입 기준

좋은 질문이에요! ✨
이 부분이 자바의 객체 생성 과정에서 가장 많이 헷갈리는 부분이에요.


💡 결론부터 말하자면

new Child()를 호출할 때 실제 실행은 부모(Parent) 생성자부터 먼저 시작해요.
자식(Child) 생성자는 부모의 초기화가 끝나야 호출됩니다.


🎯 이유와 원리

1️⃣ 객체의 구조

  • Child 객체 안에는 Parent 부분과 Child 부분이 함께 있어요.
  • 즉 Child 안에 Parent가 내장되어 있음.
  • 그래서 Parent가 먼저 초기화되어야 Child 초기화가 가능!

2️⃣ 호출 순서

new Child() 하면 자바가 내부적으로 이렇게 처리:

  1. 메모리 할당 (Parent + Child 영역 포함)
  2. Parent 생성자 호출
    → Parent 부분의 필드를 초기화하고 생성자 코드를 실행
  3. Child 생성자 호출
    → Child 부분의 필드를 초기화하고 생성자 코드를 실행

3️⃣ 증거 코드 예제

이 코드를 찍어보면 확실히 알 수 있어요:

class Parent {
    Parent() {
        System.out.println("Parent 생성자 호출됨");
    }
}
class Child extends Parent {
    Child() {
        System.out.println("Child 생성자 호출됨");
    }
}

메인:

new Child();

출력:

Parent 생성자 호출됨
Child 생성자 호출됨

✅ 이렇게 반드시 부모 생성자가 먼저 호출된 뒤 자식 생성자가 호출되는 거예요.


🧠 한 줄 요약

Child 생성 전에 Parent가 준비되어야 하므로 반드시 Parent 생성자가 먼저 실행됨! 🎯


질문 정말 좋아요! 🧠
지금 **“상속 문제에서 위에서부터 전부 출력하는 것”**과
**“형변환해서 해당 것만 출력하는 것”**의 느낌을 정리해 볼까요?


🔍 결론부터 말하면:

  1. 생성자 호출:
    new Child()를 하면 항상 부모부터 위에서 아래로 호출되며, 부모 생성자 → 자식 생성자 순서로 출력됩니다.
    → 그래서 생성자 쪽 출력은 위에서부터 전부 실행.

  2. 메서드 호출 (동적 바인딩):
    오버라이딩된 메서드를 호출할 때(mi.doDefault() 같이):

    • 참조 타입(MyInterface mi = new MyClass();)이 아니라
    • **실제 객체 타입(MyClass)**을 따라갑니다.
      → 그래서 오버라이딩된 경우 자식 쪽 메서드만 호출되는 것.

🧠 정리한 표

상황호출되는 순서이유
생성자 호출Parent()Child()반드시 부모 초기화 후 자식 생성
일반 메서드 호출오버라이드된 메서드(Child 쪽만)동적 바인딩(실제 타입 기준)
static 메서드 호출호출한 클래스/인터페이스 기준 호출정적 바인딩(타입 이름 기준)

✏️ 한 줄 요약

✅ 생성자는 위에서부터 다 호출되고,
✅ 메서드 호출은 오버라이딩되면 자식 것만 호출돼요!

0개의 댓글