제 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의 모든 기능 사용 가능
자식 인스턴스를 만들면,
그 안에는 부모의 멤버 + 자식의 멤버가 모두 포함된 “2단 서랍장”이 만들어진다고 생각하기!
위층: 부모 멤버, 아래층: 자식 멤버
• 상속 = 멤버 포함 (코드 복사 아님!)
• 자식 인스턴스는 하나지만, 그 안에는 부모 멤버까지 포함
• 이 구조 덕분에 자식 객체는 부모의 변수와 메서드를 자유롭게 사용할 수 있음
• 단, private 멤버는 상속되지만 직접 접근은 불가
Bird b = new Duck(); // 업캐스팅: 2층 서랍인데, 부모 타입으로만 접근 가능
Duck d = (Duck) b; // ✅ 다운캐스팅: 자식 타입으로 다시 열어보기!
• 업캐스팅:
“2층짜리 서랍장을 1층짜리(부모 서랍)로만 보고 쓰는 것 (자식 → 부모)”
• 다운캐스팅:
“1층짜리인 줄 알았던 서랍장을 다시 2층으로 돌려서 자식 칸까지 열어보는 것”
Java의 모든 클래스의 최상위 조상은 Object
클래스
모든 클래스는 명시적으로 extends
하지 않으면, 자동으로 extends Object가 적용됨
다중상속 : 한 클래스가 둘 이상의 부모 클래스로부터 동시에 상속을 받는 것
// ❌ Java에서는 이렇게 두 클래스 동시에 상속 불가능
class C extends A, B { ... } // 컴파일 에러!
대안: 인터페이스로 다중상속 효과 구현
“부모의 생성자는 물려받지 않음, 자식 클래스의 초기화는 자식 스스로에게 책임이 있음”
자식 클래스는 부모의 변수와 메서드는 상속받지만,
생성자(Constructor)는 상속되지 않음
→ '자식은 부모의 모든 걸 가지지만,
자식이 어떤 모습으로 태어날지는 자식이 책임져야 하니까!'
자식 생성자 안에서 부모 생성자를 명시적 or 암묵적으로 호출함
→ 자식 클래스의 초기화는 자식이 결정해야 하므로,
부모 생성자를 직접 호출해서 필요한 준비를 명확하게 요청해야함
super();
// 부모 생성자 호출
(생략해도 Java 컴파일러가 자동으로 첫 줄에 삽입해줌, 보이진 않지만...)
자식 객체 생성 시 → ✅ 부모 생성자 먼저 호출 → 그다음 자식 생성자 실행
키워드 의미 사용 목적 super(...)
부모 생성자 호출 부모 클래스 초기화 this(...)
같은 클래스의 다른 생성자 호출 자식 클래스 내부 생성자 재사용 this(...)가 첫 줄이면 → super(...)는 자동 삽입되지 않음
그 때는this(...)가 호출한 자식 클래스 내부의 다른 생성자 안에 super(...)가 자동 삽입✅ 부모 생성자는 반드시 한 번 호출된다는 말
종은 같으나, 각 객체가 서로 다른 특징을 가지고 동작하는 현상
상위 자료형은 하위 자료형보다 보유한 변수나 메서드 수는 적을지라도, 가리킬 수 있는 객체의 범위는 더 넓다 (당연)
부모는 “새”, 자식은 “오리”, “독수리”, “참새”일 때
“나는 그냥 새를 날게 하겠어!”
→ 실제 누가 날지(오리인지, 독수리인지)는 그때그때 달라짐
→ 이게 바로 다형성
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라는 객체 인스턴스 자체에 저장된 값이 바뀐 것
}
}