Programming

[JAVA] 이것이 자바다(3판) 7장 상속 확인문제 답안

아란정 2025. 4. 23. 19:59

6. Parent 클래스를 상속해서 Child 클래스를 다음과 같이 작성했는데, Child 생성자에서 컴파일 에러가 발생했습니다. 그 이유와 해결 방법을 설명해보세요.

public class Parent {
    public String name;
    
    // 매개변수가 있는 생성자
    public Parent(String name) {
        this.name = name;
    }
}
public class Child extends Parent {
    public int studentNo;
    
    public Child(String name, int studentNo) {
        this.name = name;
        this.studentNo = studentNo;
    }
}

부모 클래스에 기본 생성자가 없고 매개변수를 갖는 생성자만 있다면 자식 생성자 선언에 컴파일 에러가 난다. 

자식 객체를 생성하면 부모 객체가 먼저 생성된 다음에 자식 객체가 생성된다. 코드로는 찾아볼 수 없지만 컴파일 과정에서 자동으로 부모 생성자를 호출하는 super();을 추가해서 부모 생성자를 실행한 후에 자식 생성자를 실행하게 된다. 

public class SmartPhone extends Phone{
	public SmartPhone(Strong model, int price){
    	// 컴파일러 자동 추가
        super();
        
        // 만약 부모 클래스 기본 생성자가 없다면
        super(model, price);
        this.model = model;
        this.price = price;
 	}
}

문제 답안도 super(name); 으로 변경해야 한다. 

 

7. Parent 클래스를 상속받아 Child 클래스를 다음과 같이 작성했습니다. ChildExample 클래스를 실행했을 때 호출되는 각 클래스의 생성자의 순서를 생각하면서 출력 결과를 작성해보세요.

public class Parent {
    public String nation;
    
    public Parent() {
        this("대한민국");
        System.out.println("Parent() call");
    }
    
    public Parent(String nation) {
        this.nation = nation;
        System.out.println("Parent(String nation) call");
    }
}
public class Child extends Parent {
    public String name;
    
    public Child() {
        this("홍길동");
        System.out.println("Child() call");
    }
    
    public Child(String name) {
        this.name = name;
        System.out.println("Child(String name) call");
    }
}
public class ChildExample {
    public static void main(String[] args) {
        Child child = new Child();
    }
}

Java에서 this(...) 호출은 “같은 클래스 내의 다른 생성자를 실행하라”는 의미다. Parent() 생성자의 첫 줄이

this("대한민국");

이기 때문에, Parent()가 실행되기 직전에 동일 클래스인 Parent의 생성자가 호출되고 그 안의 코드가 먼저 수행된다.

public Parent(String nation) { … }
  1. new Parent()Parent() 실행
  2. this("대한민국") 만나면 → Parent(String nation)으로 이동
  3. Parent(String nation)에서 "Parent(String nation) call" 출력
  4. Parent(String nation) 종료 후 → 다시 Parent()로 돌아와 "Parent() call" 출력

생성자 체이닝(constructor chaining) 

즉, this("대한민국") 때문에 Parent() 내에서 Parent(String nation) 생성자로 제어가 넘어간다.

 

8. Tire 클래스를 상속받아 SnowTire 클래스를 작성했다. 출력 결과를 작성해보시오.

실제 문제는 자식 객체를 부모 클래스에 할당했을 때 부모 클래스(tire)의 run은 부모 메서드를 호출할지 자식 메서드를 호출할 지를 묻는 문제였다. 일반적으로는 부모는 자식을 모르기 때문에 자식 메서드를 사용할 수 없다.

tire의 Static type은 컴파일러가 부여한 Tire이지만,  runtime type은 SnowTire이다. 자바 런타임에서는 참조변수의 runtime 객체를 확인하기 때문에 자식의 메서드가 호출된다. 

에엥? tire.run()은 SnowTire의 메소드 영역에서 run을 찾게 된다는 말인가? 그럼 tire 가 SnowTire랑 동일한 메모리 번지를 갖는다는건가? 

우선, 클래스는 static에 올라가고, 내부 변수인 tire은 stack 프레임으로 new SnowTire()을 가리킨다. 런타임에 tire는 SnowTire를 가리키고 있으니 Method Area에서 SnowTire 클래스에 있는 run() 오버라이드 메서드를 호출한다.

[ Stack ]            [ Heap ]                     [ Method Area ]
변수, 참조              실제 객체                  	 클래스, 메서드 정의

Tire tire ────┐      ┌────────────┐           ┌---------------------------┐
              └─────▶│ SnowTire   │──────▶    │ SnowTire.run()            │
                     │ (상속받음)   │           │ Tire.run()                │
                     └────────────┘           └---------------------------┘

그럼 이제 문제를 봐보자.

public class SnowTireExample {
    public static void main(String[] args) {
        SnowTire snowTire = new SnowTire();
        Tire tire = snowTire; //자동 타입 변환

        snowTire.run();
        tire.run();
        
        
        // instanceof 이해가 잘되지 않아 추가해본 부분
       	if (snowTire instanceof Tire) {
            System.out.println("snowTire is instance of Tire");
        } else {
            System.out.println("snowTire is not instance of Tire");
        }
        if (tire instanceof SnowTire) {
            System.out.println("tire is instance of SnowTire");
        } else {
            System.out.println("tire is not instance of SnowTire");
        }
    }
}

답은 둘 다 자식 메서드를 호출한다. 

  • 자식 instanceof 부모객체 : 이건 항상 true
  • 부모(자식객체할당) instanceof 자식객체 : 이건 왜 true지?
SnowTire snowTire = new SnowTire();  
Tire tire = snowTire;  // ← Stack에 Tire 타입 변수에 담았을 뿐, 가리키는 객체(Heap)는 SnowTire  

// ① snowTire 변수에 담긴 실제 객체가 SnowTire
//   → SnowTire는 Tire의 서브클래스이므로 instanceof Tire는 true
System.out.println(snowTire instanceof Tire);       // true  
// ② tire 변수에 담긴 실제 객체도 여전히 SnowTire
//   → instanceof는 “객체가 해당 클래스 혹은 서브클래스인지”를 물으므로 역시 true
System.out.println(tire instanceof SnowTire);   // true

instanceof변수의 선언된 타입이 아니라 실제 객체의 타입을 보고 판단하기 때문에 true가 나온다. 

  • 선언된 타입(Static Type): 컴파일러가 변수에 부여하는 타입
  • 실제 객체 타입(Runtime Type): new SnowTire()로 생성된 진짜 클래스.
  1. 변수의 타입 ≠ 객체의 타입
    • 변수 선언부(Tire tire)는 “이 변수로 호출할 수 있는 메서드”를 제한
    • instanceof는 “그 변수에 들어있는 객체의 실제 타입”을 검사
  2. 객체는 한 번 생성되면 바뀌지 않는다
    • new SnowTire()로 만든 객체는 언제나 SnowTire
    • 부모 타입 변수에 담아도, 객체 자체가 Tire로 바뀌진 않는다
  3. instanceof 동작 원칙
    • x instanceof A
      • x의 실제 클래스가 A이거나
      • A의 서브클래스일 때만 true

 

11. MainActivity의 onCreate()를 실행할 때 Activity의 onCreate()도 실행시키고 싶습니다. 밑줄(빈칸)에 들어갈 코드를 작성해보세요,

public class Activity {
    public void onCreate() {
        System.out.println("기본적인 실행 내용");
    }
}
public class MainActivity extends Activity {
    @Override
    public void onCreate() {
        (        ).onCreate();
        System.out.println("추가적인 실행 내용");
    }
}

 

onCreate()는 인스턴스 메서드이므로 어떤 객체의 메서드를 실행할 지 명시해주어야 한다. 

super.onCreate();

 

  • super.onCreate(...)는 현재 this 객체의 부모(Activity) 구현을 호출하는 문법
  • 반면 Activity.onCreate()처럼 클래스 이름으로 쓰는 건 정적(static) 호출 문법인데, onCreate는 static이 아니어서 문법 오류