목차
<하위 클래스로 형 변환, 다운 캐스팅>
앞에서 상위 클래스로 형 변환이 묵시적으로 이루어지는 과정을 알아봤다.
여기에서 다시 하위 클래스로 형 변환이 되는 과정을 알아보자.
위와 같은 계층 구조에서 상위 클래스 자료형으로 선언하는 코드가 가능하다.
Animal = new Human(); // 상위 클래스 자료형으로 선언
이때 생성된 인스턴스 Human은 Animal형입니다. 이렇게 Animal형으로 형 변환이 이루어진 경우엔 Animal의 메서드와 멤버 변수만 사용이 가능하다.
따라서 필요에 따라 다시 원래 인스턴스의 자료형으로 되돌아가야 하는 경우가 필요하다.
이렇게 상위 클래스로 형 변환되었던 하위 클래스를 다시 원래 자료형으로 형 변환하는 것을 '다운 캐스팅(down casting)이라고 한다.
instanceof
상속 관계를 생각해 보면 모든 인간은 동물이지만 모든 동물이 인간이 아니다. 따라서 다운 캐스팅을 하기 전에 상위 클래스로 형 변환된 인스턴스의 원래 자료형을 확인해야 변환할 때 오류를 막을 수 있다.
이를 확인하는 예약어가 바로 'instanceof'이다.
이것은 이렇게 사용할 수 있다.
Animal hAnimal = new Human();
if(hAnimal instanceof Human){ //hAnimal 인스턴스 자료형이 Human형이라면
Human human = (Human)hAnimal; //인스턴스 hAnimal을 Human형으로 다운 캐스팅
}
위 코드에서 참조 변수 hAnimal은 원래 Human형으로 생성되었는데, Animal형으로 형 변환되었다.
instanceof 예약어는 왼쪽에 있는 변수의 원래 인스턴스형이 오른쪽 클래스 자료형인지 확인한다.
instanceof의 반환 값이 참(true)이면 다운 캐스팅을 하는데,
이때 Human human = (Human)hAnimal; 문장과 같이 명시적으로 자료형을 써주어야 한다.
상위 클래스로는 묵시적으로 형 변환이 되지만, 하위 클래스로 형 변환을 할 때는 명시적으로 해야 하기 때문이다.
만약 instanceof로 인스턴스형을 확인하지 않는다면 오류가 발생할 수 있다.
원래 자료형이 Human형이 아닌 경우,
Animal ani = new Tiger();
Human h = (Human)ani;
이렇게 해도 컴파일 오류는 나지 않는다.
일단 Tiger 인스턴스는 Animal형으로 자동으로 형 변환이 된다. Human형 변수 h와 Human형으로 강제 형 변환되는 ani의 형이 Human으로 동일하므로 컴파일 오류가 발생하지 않은 것이다.
그러나 코드를 실행하면 실행 오류가 발생한다.
따라서 참조 변수의 원래 인스턴스형을 정확이 확인하고 다운 캐스팅을 해야 안전하며, 이때 instanceof를 사용하는 것이다.
package polymorphism;
import java.util.ArrayList;
class Animal{
public void move() {
System.out.println("동물이 움직입니다.");
}
}
class Human extends Animal {
public void move(){
System.out.println("사람이 두 발로 걷습니다.");
}
public void readBook() {
System.out.println("사람이 책을 읽습니다.");
}
}
class Tiger extends Animal {
public void move() {
System.out.println("호랑이가 네 발로 뜁니다.");
}
public void hunting() {
System.out.println("호랑이가 사냥을 합니다.");
}
}
class Eagle extends Animal{
public void move() {
System.out.println("독수리가 하늘을 납니다.");
}
public void flying() {
System.out.println("독수리가 날개를 펼치고 날아오릅니다.");
}
}
public class AnimalTest {
ArrayList<Animal> aniList = new ArrayList <Animal>();
//배열의 자료형은 Animal로 지정
public static void main(String[] args) {
AnimalTest aTest = new AnimalTest();
aTest.addAnimal();
System.out.println("=======원래 형으로 다운 캐스팅=======");
aTest.testCasting();
}
public void addAnimal() {
aniList.add(new Human());
aniList.add(new Tiger());
aniList.add(new Eagle());
//ArrayList에 추가되면서 Animal형으로 형 변환
for(Animal ani : aniList) {
ani.move();
} //배열 요소를 Animal형으로 꺼내서 move()를 호출하면 재정의된 메서드가 호출
}
public void testCasting() {
for(int i = 0; i < aniList.size(); i++) { //모든 배열의 요소를 하나씩 돌면서
Animal ani = aniList.get(i); //Animal형으로 가져옴
if(ani instanceof Human) { //Human이면
Human h = (Human)ani; //Human형으로 다운 캐스팅
h.readBook();
}
else if(ani instanceof Tiger) {
Tiger t = (Tiger)ani;
t.hunting();
}
else if (ani instanceof Eagle) {
Eagle e = (Eagle)ani;
e.flying();
}
else {
System.out.println("지원되지 않는 형입니다.");
}
}
}
}
각 동물 클래스를 인스턴스로 생성하여 Animal형으로 선언한 배열에 추가. 이러면 배열에 추가되는 요소의 자료형은 모두 Animal형으로 변환된다.
이때 호출할 수 있는 메서드는 Animal클래스에 선언된 메서드 뿐이다.
향상된 for문을 사용해 모든 배열의 요소를 하나씩 꺼내 move()메서드를 호출하면 재정의된 메서드가 호출된다.
하지만 배열 요소가 Animal형이므로 readBook(), hunting(), flying()메서드는 사용할 수 없다.
이들을 호출하기 위해서는 다시 원래 자료형으로 다운 캐스팅되어야 한다.
'instanceof를 활용'하여 실제 인스턴스형을 살펴본 후에 다운 캐스팅을 하면 각 클래스에 있는 메서드를 호출할 수 있다.
[Do it! 자바 프로그래밍 입문] 도서로 공부하며 정리한 글입니다.