JAVA

다운 캐스팅, instanceof

seungmin576 2024. 12. 23. 17:02

하위 클래스로 형 변환, 다운 캐스팅

앞에서 상위 클래스로 형 변환이 묵시적으로 이루어지는 과정을 알아보았다.

여기선 다시 하위 클래스로 형 변환이 되는 과정을 살펴본다.

위와 같은 계층 구조에서 상위 클래스를 자료형으로 선언하는 Animal ani = new Human(); 코드를 쓸 수 있다.

이때 생성된 인스턴스 Human은 Animal형이다.

이렇게 Animal형으로 형 변환이 이루어진 경우에는 Animal 클래스에서 선언한 메서드와 멤버 변수만 사용할 수 있다.

다시말해 Human 클래스에 더 많은 메서드가 구현되어 있고 다양한 멤버 변수가 있다고 하더라도 자료형이 Animal형인 상태에서는 사용할 수가 없다.

따라서 필요에 따라 다시 원래 인스턴스의 자료형으로 되돌아가야 하는 경우가 있다.

이렇게 상위 클래스로 형 변환되었던 하위 클래스를 다시 원래 자료형으로 형 변환하는 것을 다운 캐스팅이라고 한다.


instanceof

상속 관계를 생각해 보면 모든 인간은 동물이지만 모든 동물이 인간은 아니다.

따라서 다운캐스팅을 하기 전에 상위 클래스로 형 변환된 인스턴스의 원래 자료형을 확인해야 변환할 때 오류를 막을 수 있다.

이를 확인하는 예약어가 바로 instaceof이다.

instanceof는 다음과 같이 사용할 수 있다.

Animal hAnimal = new Human();
if(hAnimal instanceof Human){
	Human human = Human(hAnimal);
}

위 코드에서 사용한 참조 변수 hAnimal은 원래 Human형으로 생성되었는데, Animal형으로 형 변환되었다.

instanceof 예약어는 왼쪽에 있는 변수의 원래 인스턴스형이 오른쪽 클래스 자료형인가를 확인한다.

코드를 보면 hAnimal이 Animal형으로 되어 있지만, 원래는 Human형으로 생성된 인스턴스인지 확인하는 것이다.

instanceof의 반환 값이 true이면 다운 캐스팅을 하는데, 이때는 Human human = (Human)hAnimal; 문장과 같이 명시적으로 자료형을 써 주어야 한다.

상위 클래스로는 묵시적으로 형 변환이 되지만, 하위 클래스로 형 변환을 할 때는 명시적으로 해야 하기 때문이다.

만약 instanceof로 인스턴스형을 확인하지 않으면 오류가 발생할 수 있다.

 

다음처럼 원래 자료형이 Human형이 아닌 경우

Animal ani = new Tiger();
Human h = (Human)ani;

위와 같이 코딩해도 컴파일 오류는 나지 않는다.

일단 Tiger 인스턴스는 Animal형으로 자동으로 형 변환이 된다.

변수 h의 자료형 Human과 강제 형 변환되는 ani(Human)형이 동일하므로 컴파일 오류는 나지 않는다.

그 대신 이 코드를 실행하면 실행 오류가 발생한다.

따라서 참조 변수의 원래 인스턴스형을 정확히 확인하고 다운 캐스팅을 해야 안전하며 이때 instanceof를 사용한다.

 

  • 원래 인스턴스형으로 다운 캐스팅하는 예제

Animal 클래스를 상속받은 여러 동물 클래스가 있다.

Human 클래스, Tiger 클래스, Eagle 클래스에는 각각 다른 메서드도 추가로 구현했다.

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 AnimalTest1{
//    public static void main(String[] args){
//        AnimalTest1 aTest = new AnimalTest1();
//        aTest.moveAnimal(new Human());
//        aTest.moveAnimal(new Tiger());
//        aTest.moveAnimal(new Eagle());
//    }
//
//    public void moveAnimal(Animal animal){  //매개 변수의 자료형이 상위 클래스
//        animal.move();  //재정의된 메서드가 호출됨
//    }
    ArrayList<Animal> aniList = new ArrayList<Animal>();

    public static void main(String[] args){
        AnimalTest1 aTest = new AnimalTest1();
        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형으로 다운 캐스팅
                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형이므로 각 클래스에서 제공하는 readBook(), hunting(), flying() 메서드를 사용할 수 없다.

다시 말해 자료형이 Animal 형인 상태에서는 Human 클래스가 제공하는 readBook() 메서드를 호출할 수 없다.

각각의 클래스에 선언된 readBook(), hunting(), flying()을 호출하기 위해서는 다시 원래 자료형으로 다운 캐스팅되어야 한다.

instanceof를 활용하여 실제 인스턴스형을 살펴본 후에 다운 캐스팅을 하면 각 클래스에 있는 메서드를 호출할 수 있다.

 

'JAVA' 카테고리의 다른 글

템플릿 메서드 (디자인패턴)  (0) 2024.12.24
추상 클래스  (0) 2024.12.24
다형성 활용하기  (0) 2024.12.23
다형성  (0) 2024.12.20
메서드 오버라이딩, 가상 메서드  (1) 2024.12.20