다운 캐스팅(DownCasting)
- 다운캐스팅은 거꾸로 부모 클래스가 자식 클래스 타입으로 캐스팅 되는 것이다.
- 다운캐스팅은 캐스팅 연산자 괄호를 생략할 수 없다
- 다운캐스팅의 목적은 업캐스팅한 객체를 다시 자식 클래스 타입의 객체로 되돌리는데 목적을 둔다. (복구)
다운 캐스팅은 부모 클래스를 자식클래스로 캐스팅하는 단순히 업캐스팅의 반대 개념이 아니다.
다운 캐스팅의 진정한 의미는 부모 클래스로 업 캐스팅된 자식 클래스를 복구하여, 본인의 필드와 기능을 회복하기 위해 있는 것이다. 즉, 원래 있던 기능을 회복하기 위해 다운캐스팅을 하는 것이다.
|
class Unit { |
|
public void attack() { |
|
System.out.println("유닛 공격"); |
|
} |
|
} |
|
|
|
class Zealot extends Unit { |
|
public void attack() { |
|
System.out.println("찌르기"); |
|
} |
|
|
|
public void teleportation() { |
|
System.out.println("프로토스 워프"); |
|
} |
|
} |
|
|
|
public class Main { |
|
public static void main(String[] args) { |
|
|
|
Unit unit_up; |
|
Zealot zealot = new Zealot(); |
|
|
|
unit_up = zealot; // 업캐스팅 |
|
|
|
// * 다운캐스팅(downcasting) - 자식 전용 멤버를 이용하기위해, 이미 업캐스팅한 객체를 되돌릴때 사용 |
|
Zealot unit_down = (Zealot) unit_up; // 캐스팅 연산자는 생략 불가능. 반드시 기재 |
|
unit_down.attack(); // "찌르기" |
|
unit_down.teleportation(); // "프로토스 워프" |
|
} |
|
} |
Copy
업캐스팅 된 객체 unit_up 에서 만일 자식 클래스에만 있는 teleportation() 메서드를 실행해야 하는 상황이 온다면, 다운 캐스팅을 통해 자식 클래스 타입으로 회귀 시킨뒤 메서드를 실행하면 된다. 만일 메서드를 한번 만 실행 할 것이라 따로 변수에 저장해둘 필요성이 없다면, 아래와 같이 다운 캐스팅을 한줄로 표현할 수도 있다.
|
((Zealot) unit_up).teleportation(); // "프로토스 워프" |
Copy
이때 캐스팅 연산자를 업캐스팅과는 달리 생략할 수 없는데, 나름의 이유가 있기 때문이다. 다운캐스팅은 곧 사용할 수 있는 객체 멤버 증가를 의미하는데, 멤버의 증가는 불안전 하다. 왜냐하면 실제 참조변수가 가리키는 객체가 무엇인지 모르기 때문에 어떠한 멤버가 추가 되는지 알수가 없다. 그래서 반드시 형변환 괄호를 기재함으로써 증가된 클래스의 멤버가 무엇인지 알게 하도록 개발자한테 알려줘야 하기 때문이다.
다운 캐스팅 주의점
앞서 다운 캐스팅의 목적은 업캐스팅한 객체를 되돌리는데 있다고 했다. 그래서 다음과 같이 업캐스팅 되지 않는 생 부모 객체 unit 일 경우, 이를 그대로 (Zealot) unit 다운캐스팅 하면 오류(ClassCastException)가 발생하게 된다.
|
Unit unit = new Unit(); |
|
|
|
// * 다운캐스팅(downcasting) 예외 - 다운캐스팅은 업스캐팅한 객체를 되돌릴때 적용 되는것이지, 오리지날 부모 객체를 자식 객체로 강제 형변환은 불가능 |
|
Zealot unit_down2 = (Zealot) unit; //! RUNTIME ERROR - Unit cannot be cast to Zealot |
|
unit_down2.attack(); //! RUNTIME ERROR |
|
unit_down2.teleportation(); //! RUNTIME ERROR |
Copy
이러한 다운 캐스팅 특성은 원래 참조 다형성에서도 불가능 했기 때문에 발생하는 것이다.
|
Zealot unit_down = new Unit(); // 참조 다형성 위배 |
Copy
위와 같은 다운 캐스팅 특성에 대해 주의해야 할 이유는 에디터에서 컴파일 에러가 발생하기 않고 런타임 에러가 발생하는 위험성이 있기 때문이다.
예를들어 기본형 캐스팅은 값의 손실만 있을 뿐 프로그램이 작동하는데는 문제없다. (3.16 → 3)
하지만 다운 캐스팅은 에디터에서는 빨간줄이 없는데 코드를 실행 도중에 갑자기 에러가 터져 프로그램이 죽어버릴 수 있다.
추가로 아무리 같은 부모 클래스를 상속하고 있더라도 형제 클래스 끼리는 서로 캐스팅이 불가능하다. 이는 잘못 판단하면 컴파일 에러와 런타임 에러 둘다 생길 수 있는 가능성이 있으니 매우 조심해야 한다.
|
class Unit { |
|
public void attack() { |
|
System.out.println("유닛 공격"); |
|
} |
|
} |
|
|
|
class Zealot extends Unit { |
|
public void attack() { |
|
System.out.println("찌르기"); |
|
} |
|
|
|
public void teleportation() { |
|
System.out.println("프로토스 워프"); |
|
} |
|
} |
|
|
|
class Marine extends Unit { |
|
public void attack() { |
|
System.out.println("총쏘기"); |
|
} |
|
|
|
public void stimpack() { |
|
System.out.println("스팀 팩"); |
|
} |
|
} |
|
|
|
public class Main { |
|
public static void main(String[] args) { |
|
|
|
Unit unit = new Unit(); |
|
Zealot zealot = new Zealot(); |
|
|
|
// * 다운캐스팅(downcasting) 예외 - 같은 상속 자식 클래스라도 구성이 같아도 타입이 다르니 불가능 |
|
Marine marine = new Marine(); // 마린 클래스 |
|
|
|
Unit unit_m = marine; // 업캐스팅 |
|
|
|
Zealot zealot_marine = (Zealot) unit_m; // 다른 자식클래스 질럿으로 다운캐스팅 |
|
zealot_marine.attack(); //! RUNTIME ERRPR - Marine cannot be cast to Zealot |
|
zealot_marine.stimpack(); //! COMPILE ERRPR - Zealot 클래스에 없는 메소드이니 에러 |
|
} |
|
} |
Copy
이 처럼 무분별한 다운캐스팅은 컴파일 시점에는 오류가 발생하지 않아도 런타임 오류를 발생시킬 가능성이 있다. 따라서 다운 캐스팅을 다룰때에는 다운 캐스팅 할 객체가 오리지날 부모 객체인지, 업캐스팅된 부모 객체인지 항상 머릿속에서 가능한지 생각해 볼 필요성이 있다.
다행인 점은 이렇게 혼동되는 객체를 구별하기 위해 도움을 주는 연산자를 자바에서 지원해준다.
instanceof 연산자
참조 캐스팅을 잘못했다가 런타임 환경에서 에러가 나 프로그램이 종료 되버리면 서비스에 크나큰 차질이 생기게 된다.
따라서 코드 디버깅을 많이 하여 미리 예방하는 것이 베스트이지만,
이마저도 부족하면 직접 업캐스팅 / 다운캐스팅 유무를 확인하여 참조 캐스팅 동작을 결정하면 된다.
이때 사용되는 것이 instanceof 연산자인데, 이 연산자는 어느 객체 변수가 어느 클래스 타입인지 판별해 true/false를 반환해준다. 사용시 주의할 점은 instanceof 연산자는 객체에 대한 클래스(참조형) 타입에만 사용할 수 있다는 점이다. (int, double 같은 primitive 타입에는 사용 불가능)
|
class Unit { |
|
// ... |
|
} |
|
|
|
class Zealot extends Unit { |
|
// ... |
|
} |
|
|
|
public class Main { |
|
public static void main(String[] args) { |
|
|
|
// * 업캐스팅 유무 |
|
Zealot zealot = new Zealot(); |
|
|
|
if (zealot instanceof Unit) { |
|
System.out.println("업캐스팅 가능"); // 실행 |
|
Unit u = zealot; // 업캐스팅 |
|
} else { |
|
System.out.println("업캐스팅 불가능"); |
|
} |
|
|
|
// * 다운스캐팅 유무 |
|
Unit unit = new Unit(); |
|
Unit unit2 = new Zealot(); |
|
|
|
if (unit instanceof Zealot) { |
|
System.out.println("다운캐스팅 가능"); |
|
} else { |
|
System.out.println("다운캐스팅 불가능"); // 실행 |
|
} |
|
|
|
if (unit2 instanceof Zealot) { |
|
System.out.println("다운캐스팅 가능"); // 실행 |
|
Zealot z = (Zealot) unit2; // 다운캐스팅 |
|
} else { |
|
System.out.println("다운캐스팅 불가능"); |
|
} |
|
} |
|
} |
'공부 STUDY > JAVA' 카테고리의 다른 글
[JAVA] 가상 메서드(virtual method) (0) | 2023.01.13 |
---|---|
[JAVA] 인터페이스(interface) (0) | 2023.01.10 |
[JAVA] ArrayList 사용법 (0) | 2023.01.09 |
[JAVA] 상속 | 다형성(Polymorphism) (0) | 2023.01.08 |
[JAVA] 상속 | 메서드 오버라이딩 (0) | 2023.01.08 |