상속관계에서의 형변환

제 4의 자료형인 객체 자료형도 형변환 가능?

✔ 자바에서 객체 자료형도 형변환 가능.
단, 상속 관계가 있는 경우에만 형변환이 인정!

상속관계인 객체 자료형의 형변환 두 종류

종류방향예시형변환 허용 여부안정성
업캐스팅자식 →(담긴다) 부모Fish f = new Shark();자동 형변환 (O)안전
다운캐스팅부모 →(담긴다) 자식Shark s = (Shark) f;
(단, f가 실제 Shark일 때)
강제 형변환 (O)조건부 안전
(잘못하면 런타임 오류)

부모 자료형(상위자료형)에 자식 자료형를 담는 것은 자연스러움

Fish f = new Fish(); 
Shark s = new Shark();
f = s; //개념 상 큰 범위(fish)에 작은 범위(shark)를 넣을 수 있음

s = (Shark) f; //강제 형변환, 하지만 지금 f는 Fish객체이므로 
//런타임 오류 (ClassCastException)

형식은 맞지만, 실제 객체가 자식(Shark)이 아닌 상태
→ 컴파일은 가능(같은 자료형이니까),
but 실행할 때(런타임) 오류 (부모인 Fish 객체만 존재)

⚠ 객체 캐스팅은 “데이터 손실” 개념이 없다.
잘못 캐스팅하면 런타임 오류가 날 뿐

다운캐스팅은 실제 객체가 자식이어야만 성공

Fish f = new Shark();   // f는 Shark 객체를 담음 (업캐스팅)
Shark s = (Shark) f;    // OK! → 다운캐스팅 성공

⚠️ 다운캐스팅이 위험한 이유

(물고기🐠와 상어🦈이야기)

"Fish는 Shark의 일부가 아니다."

즉, 모든 Fish가 Shark는 아니기 때문에, Java는 위험할 수 있다고 판단한다.

Java : “정말 이 Fish가 Shark에서 태어난 객체라는 걸 네가 보장할 수 있어?
그렇다면 니가 책임지고 형변환해. (명시적 캐스팅)”

Fish f = new Fish(); // 그냥 물고기일 뿐인데
Shark s = (Shark) f; // 강제로 "이거 상어야!" 라고 우기기

👉 진짜 상어가 아니라면 상어 전용 기능(날카로운 이빨같은) 쓰려다 큰일 나는 거임!
그래서 자바는 이런 잘못된 우김을 막기 위해 런타임에서 검사하고 예외를 던진다.

진짜 상어였으면?

Fish f = new Shark(); // OK 업캐스팅
Shark s = (Shark) f; // OK 다운캐스팅 (f는 실제로 Shark니까)

👉 Shark의 모든 기능 사용 가능


상속의 메모리 구조

자료형은 n단 서랍장

자식 인스턴스를 만들면,
그 안에는 부모의 멤버 + 자식의 멤버가 모두 포함된 “2단 서랍장”이 만들어진다고 생각하기!

위층: 부모 멤버, 아래층: 자식 멤버

• 상속 = 멤버 포함 (코드 복사 아님!)

• 자식 인스턴스는 하나지만, 그 안에는 부모 멤버까지 포함
• 이 구조 덕분에 자식 객체는 부모의 변수와 메서드를 자유롭게 사용할 수 있음
• 단, private 멤버는 상속되지만 직접 접근은 불가

Bird b = new Duck();       // 업캐스팅: 2층 서랍인데, 부모 타입으로만 접근 가능
Duck d = (Duck) b;         // ✅ 다운캐스팅: 자식 타입으로 다시 열어보기!

• 업캐스팅:
“2층짜리 서랍장을 1층짜리(부모 서랍)로만 보고 쓰는 것 (자식 → 부모)”
• 다운캐스팅:
“1층짜리인 줄 알았던 서랍장을 다시 2층으로 돌려서 자식 칸까지 열어보는 것”


Object

Java의 모든 클래스의 최상위 조상은 Object 클래스

모든 클래스는 명시적으로 extends하지 않으면, 자동으로 extends Object가 적용됨


다중상속 금지 원칙

다중상속 : 한 클래스가 둘 이상의 부모 클래스로부터 동시에 상속을 받는 것

// ❌ Java에서는 이렇게 두 클래스 동시에 상속 불가능
class C extends A, B { ... } // 컴파일 에러!

대안: 인터페이스로 다중상속 효과 구현


super( )

“부모의 생성자는 물려받지 않음, 자식 클래스의 초기화는 자식 스스로에게 책임이 있음”

✅ 부모의 생성자는 상속되지 않는다.

자식 클래스는 부모의 변수와 메서드는 상속받지만,
생성자(Constructor)는 상속되지 않음

→ '자식은 부모의 모든 걸 가지지만,
자식이 어떤 모습으로 태어날지는 자식이 책임져야 하니까!'

✅ 자식은 생성자에서 부모 생성자 호출을 책임져야 한다.

자식 생성자 안에서 부모 생성자를 명시적 or 암묵적으로 호출함

→ 자식 클래스의 초기화는 자식이 결정해야 하므로,
부모 생성자를 직접 호출해서 필요한 준비를 명확하게 요청해야함

super(); // 부모 생성자 호출
(생략해도 Java 컴파일러가 자동으로 첫 줄에 삽입해줌, 보이진 않지만...)

자식 객체 생성 시 → ✅ 부모 생성자 먼저 호출 → 그다음 자식 생성자 실행

키워드의미사용 목적
super(...)부모 생성자 호출부모 클래스 초기화
this(...)같은 클래스의 다른 생성자 호출자식 클래스 내부 생성자 재사용

this(...)가 첫 줄이면 → super(...)는 자동 삽입되지 않음
그 때는this(...)가 호출한 자식 클래스 내부의 다른 생성자 안에 super(...)가 자동 삽입

✅ 부모 생성자는 반드시 한 번 호출된다는 말


다형성

다형성(Polymorphism)

  • 종은 같으나, 각 객체가 서로 다른 특징을 가지고 동작하는 현상

  • 상위 자료형은 하위 자료형보다 보유한 변수나 메서드 수는 적을지라도, 가리킬 수 있는 객체의 범위는 더 넓다 (당연)

부모는 “새”, 자식은 “오리”, “독수리”, “참새”일 때

“나는 그냥 새를 날게 하겠어!”
→ 실제 누가 날지(오리인지, 독수리인지)는 그때그때 달라짐
→ 이게 바로 다형성

class Bird {
    void fly() {
        System.out.println("Bird가 난다");
    }
}

class Duck extends Bird {
    void fly() {
        System.out.println("오리가 난다");
    }
}

class Eagle extends Bird {
    void fly() {
        System.out.println("독수리가 난다");
    }
}
Bird b1 = new Duck();    // 업캐스팅
Bird b2 = new Eagle();   // 업캐스팅

b1.fly();   // "오리가 난다" (자식의 fly() 호출)
b2.fly();   // "독수리가 난다"

Bird 타입으로 선언했지만,
실제 객체가 누구냐에 따라 자식의 오버라이딩 메서드가 실행
→ 다형성 + 동적 바인딩

동적 바인딩

실행 시점(Run-time)에,
참조 변수가 가리키는 실제 객체의 오버라이딩된 메서드를 호출하는 것

(동적 바인딩 문제)

abstract class Vehicle { public int speed() { return 0; } 
class Car extends Vehicle { public int speed() { return 60; } 
class RaceCar extends Car { public int speed() { return 150; } 
...  
RaceCar racer = new RaceCar(); // this=racer =>RaceCar()
Car car = new RaceCar(); // this=car =>RaceCar()
Vehicle vehicle = new RaceCar(); // this=vehicle =>RaceCar()
System.out.println(racer.speed() + ", " + car.speed() + ", " + vehicle.speed());  

출력 : 150, 150, 150


상속 학습정리 퀴즈 中

생성자와 상속의 초기화 순서
+ 생성자 매개변수 처리
+ 객체의 멤버 접근

package animal;
class Bird {
	protected String name="새";
	boolean fly;
	public Bird(Boolean f) {
		fly = f; // this.fly = f;와 같음 (=이 객체 안에 있는 멤버변수 fly)
	}
}

package animal;
class Duck extends Bird {
	String sound="quack";
}


package animal;
class UseTest {
	public static void main(String[] args){
	        Bird b=new Bird(); // (오류) Bird b = new Bird(false);로 
            //✅ 생성자에 매개변수 전달
            Duck d=new Duck(); // (오류) Duck의 기본 생성자에 
            //super(false);만 추가하면 가능

	        d.sound=JJack JJack; // d가 가리키는 객체의 sound 필드 값을 바꾼 것. 
            // ✅ d라는 객체 인스턴스 자체에 저장된 값이 바뀐 것
	}
}
profile
아이들의 가능성을 믿었던 마음 그대로, 이제는 나의 가능성을 믿고 나아가는 중입니다.🌱

0개의 댓글