-
Java - 상속 다형성(다운 캐스팅과 instanceof)7카테고리 없음 2023. 11. 3. 13:13
다운 캐스팅과 instanceof
하위 클래스로 형 변환, 다운 캐스팅
위와 같은 계층 구조에서 상위 클래스를 자료형으로 선언하는 Animal ani = new Human(); 코드를 사용할 수 있다. 이 때 생성된 인스턴스 Human은 Animal형이다. 이렇게 Animal형으로 형 변환이 이루어진 경우에는 Animal 클래스에서 선언한 메서드와 멤버 변수만 사용할 수 있다. 다시 말해 Human 클래스에 더 많은 메서드가 구현되어 있고 더 다양한 멤버 변수가 있다고 하더라도 자료형이 Animal형인 상태에서는 사용할 수가 없다. 따라서 필요에 따라 다시 원래 인스턴스의 자료형(여기서는 Human형)으로 되돌아가야 하는 경우가 있다. 이렇게 상위 클래스로 형 변환되어 있던 하위 클래스를 다시 원래 자료형으로 형(type) 변환하는 것을 다운 캐스팅(down casting)이라고 한다.
instanceof
상속 관계를 생각해 보면 모든 인간은 동물이지만 모든 동물이 인간은 아니다. 따라서 다운캐스팅을 하기 전에 상위 클래스로 형 변환된 인스턴스의 원래 자료형을 확인해야 변환할 때 오류를 막을 수 있다. 이를 확인하는 예약어가 바로 instanceof이다.
Animal hAnimall = new Human(); if(hAnimal instanceif Human){ //hAnimal 인스턴스 자료형이 Human형이라면 Human human = (Human)hAnimal; //인스턴스 hAnimal을 Human형으로 다운 캐스팅 }
위 코드에서 사용한 참조 변수 hAnimal은 원래 Human형으로 생성되었는데, Animal형으로 형 변환되었다. instanceof 예약어는 왼쪽에 있는 인스턴스 변수의 원래 인스턴스 형(type)이 오른쪽에 위치한 클래스 자료 형(type)인가를 확인한다. 즉 hAnimal이 Animal형으로 되어 있지만, 원래는 Human형으로 생성된 인스턴스인지를 확인하는것이다. instanceof의 반환 값이 true이면 다운 캐스팅을 하는데, 이때는 Human humna = (Human)hAnimal; 문장과 같이 명시적으로 자료형을 써주어야 한다. 상위 클래스로는 묵시적으로 형 변환이 되지만, 하위 클랫로 형변환을 할 때는 명시적으로 해야 하기 때문이다. 만약 instanceof로 인스턴스형을 확인하지 않으면 오류가 발생할 수 있다.
생성시 자료형이 아닌 것으로 다운 캐스팅
Animal ani = new Tiger(); Human h = (Human)ani;
위와 같이 코딩해도 컴파일 오류는 발생하지 않는다. 왜 일까? 일단 Tiger 인스턴스는 Animal 형으로 자동으로 형 변환이 된다. 변수 h의 자료형 Humna과 강제 형 변환되는 ani의(Human)형이 동일하므로 컴파일 오류는 나지 않는 것이다. 그 대신 이 코드를 실행하면 실행 오류가 발생한다.
따라서 참조 변수의 원래 인스턴스형을 정확히 확인하고 다운 캐스팅을 해야 안전하며 이때 instanceof를 사용한다. 그러면 원래 인스턴스형으로 다운 캐스팅하는 예를 살펴보자
Animal 클래스를 상속받은 여러 동물 클래스
package polymorphism; import java.util.ArrayList; //상위 클래스 Animal class Animal{ public void move(){ System.out.println("동물이 움직인다.") } } // Animal을 상속받은 Human클래스 class Human extends Animal{ public void move(){ System.out.println("사람이 두 발로 걷습니다."); } public void readBook(){ System.out.println("사람이 책을 읽는다."); } } // Animal을 상속받은 Tiger클래스 class Tiger extends Animal{ public void move(){ System.out.println("호랑이가 네 발로 뜁니다."); } public void hunting(){ System.out.println("호랑이가 사냥을 한다."); } } //Animal을 상속받은 Eagle클래스 class Egle extends Animal{ public void move(){ System.out.println("독수리가 하늘을 난다."); } public void flying(){ System.out.println("독수리가 날개를 쭉 펴고 멀리 날아간다.") } } public class AnimalTest{ //배열의 자료형은 Animal로 지정 ArrayList<Animal> aniList = new ArrayList<Animal>(); public static void main(String[] args){ AnimalTest aTest = new AnimalTest(); aTest.addAnimal(); //사람이 두 발로 걷습니다. //호랑이가 네 발로 뜁니다. //독수리가 하늘을 납니다. //원래 형으로 다운 캐스팅 aTest.testCasting(); //사람이 책을 읽습니다. //호랑이가 사냥을 합니다. //독수리가 날개를 쭉 펴고 멀리 날아갑니다. } public void addAnimal(){ // ArrayList에 추가되면서 Animal형으로 형 변환 aniList.add(new Human()); aniList.add(new Tiger()); aniList.add(new Eagle()); // 배열 요소를 Animal형으로 꺼내서 move()를 //호출함녀 재정의된 함수가 호출됨 for(Animal ani : aniList){ ani.move(); } public void testCasting(){ for(int i = 0; i < aniList.size();i++){ Animal ani = aniList.get(i); if(ani instanceof Human){ Human h = (Human)ani; 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형 이므로 각 클래스에서 제공하는 메서드는 사용할 수 없다. 각각 클래스에서만 선언된 고유 메서드를 호출하기 위해서는 다시 원래 자료현으로 다운 캐스팅되어야 한다. instanceof를 활용하여 실제 인스턴스형을 살표본후 다운 캐스팅을 하면 각 클래스에 있는 메서드를 호출할 수 있다.
[출저 - Do it! 자바 프로그래밍 입문 , 박은종]
http://www.easyspub.co.kr/20_Menu/BookView/A001/267/PUB