[그린컴퓨터] Server/JAVA(객체 지향 프로그래밍)

다운 캐스팅과 instanceof { 다운 캐스팅이란, instanceof란 }

Ben의 프로그램 2023. 5. 31. 20:06
728x90

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

  • 앞에서 상위 클래스로 형 변환이 묵시적으로 이루어지는 과정을 알아보았습니다. 
  • 여기에서는 다시 하위 클래스로 형 변환이 진행되는 과정을 살펴보겠습니다.

  • 위 그림과 같은 계층 구조에서 다음과 같이 자료형을 선언했다고 생각해 보겠습니다.
Animal ani = new Human( );
  • 이때 생성된 인스턴스 Human 은 Animal 형입니다. 
  • 자연스럽게 Human 은 Animal 클래스에서 선언한 메서드와 멤버 변수만 사용할 수 있습니다. 
  • 다른 말로 하자면, Human 클래스에 더 많은 메서드가 구현되어 있고 다양한 멤버 변수가 있다고 하더라도 자료형이 Animal 형인 Human 은 사용할 수 없다는 것을 의미합니다. 
  • 필요에 따라 다시 원래 인스턴스의 자료형으로 되돌아가서 인스턴스 클래스에서 선언한 멤버 변수와 메서드를 사용해야 할 때가 있습니다. 
  • 이렇게 상위 클래스 형으로 형 변환되었던 하위 클래스를 다시 원래 자료형으로 형 변환하는 것을 다운 캐스팅(down casting)이라고 합니다. 

instanceof 란?

  • instanceof 는 다운 캐스팅을 할 때 꼭 필요한 존재입니다. 왜 필요한지부터 이해하고 가겠습니다. 
  • 상속 관계를 생각해 보면 모든 인간은 동물이지만 모든 동물이 인간은 아닙니다. 
  • 따라서 다운 캐스팅을 하기 전에 상위 클래스로 형 변환된 인스턴스의 원래 자료형을 확인해야 다운 캐스팅 변환할 때 오류를 막을 수 있습니다. 
  • 이를 확인하는 예약어가 바로 Instanceof 입니다. 
  • instanceof 는 다음과 같이 사용할 수 있습니다. 
Animal hAnimal = new Human( );
if (hAnimal instanceof Human) { 
    Human human = (Human)hAnimal;
    human.move();
}
  • 위 코드에서 사용한 참조 변수 hAnimal 은 원래 Human 형으로 생성되었는데, Anumal 형으로 묵시적 클래스 형 변환이 되었습니다. 
  • instanceof 예약어는 왼쪽에 있는 변수의 원래 인스턴스형이 오른쪽 클래스 자료형인가를 확인합니다. 
  • 다른 말로 하면, hAnimal 이 현재는 Animal 자료형으로 되어있지만, 원래는 Human 형으로 생성된 인스턴스인지 확인하는 것입니다. 
  • 여기서 재미있는 점은 instanceof 로 인스턴스형을 확인하지 않으면 컴파일 오류가 아니라 런타임 오류가 발생한다는 점입니다. 
Animal ani = new Tiger();
Human h = (Human)ani;
  • 원래 자료형이 Human 이 아닌 참조 변수 ani 를 Human 자료형으로 다운 캐스팅을 하면 컴파일 오류는 나지 않습니다.
  • 왜냐하면 일단 Animal 자료형으로 묵시적 자동 형 변환이 진행된 Tiger 의 인스턴스는 강제적으로 Human 형으로 형 변환이 되었습니다. 해당 ani 변수가 강제적으로 Human 형으로 형 변환이 되었으니 마찬가지로 Human 자료형인 h 변수에 대입하는 것에 문제는 없습니다. 
  • 그런데 이 코드를 실행하면 실행 오류가 발생합니다.
  • 따라서 참조 변수의 원래 인스턴스 형을 정확히 확인하고 다운 캐스팅을 해야 안전하며 이때 instanceof 예약어를 사용합니다. 
  • 아무튼 if 문을 따라서 instanceof 의 반환 값이 true 면 다운 캐스팅을 진행하는데

    Human human = (Human)hAnimal;

    위 코드와 같이 명시적으로 자료형을 써주어야 합니다. 
  • 왜냐하면 상위 클래스로는 묵시적으로 형 변환이 되지만, 하위 클래스로 형 변환을 할 때는 명시적으로 해야 하기 때문입니다. 
  • 동물 클래스를 예시로 보면서 이해해보도록 하겠습니다.
package polymorphism;
import java.util.ArrayList;

class Animal2 {
	public void move() {
		System.out.println("동물이 움직입니다.");
	}
}

class Human2 extends Animal2 {
	@Override
	public void move() {
		System.out.println("사람이 두 발로 걷습니다.");
	}
	
	public void readBook() {
		System.out.println("사람이 책을 읽습니다.");
	}
}

class Tiger2 extends Animal2 {
	@Override
	public void move() {
		System.out.println("호랑이가 네 발로 뜁니다.");
	}
	
	public void hunting() {
		System.out.println("호랑이가 사냥을 합니다.");
	}
}

class Eagle2 extends Animal2 {
	@Override
	public void move() {
		System.out.println("독수리가 하늘을 납니다.");
	}
	
	public void flying() {
		System.out.println("독수리가 날개를 쭉 펴고 멀리 날아갑니다.");
	}
}

public class AnimalTest2 {
	ArrayList<Animal2> aniList = new ArrayList<Animal2>();
	
	public static void main(String[] args) {
		AnimalTest2 aTest = new AnimalTest2();
		aTest.addAnimal();
		System.out.println("원래 형으로 다운 캐스팅");
		
	}
	
	public void addAnimal() {
		aniList.add(new Human2());
		aniList.add(new Tiger2());
		aniList.add(new Eagle2());
		
		for (Animal2 ani : aniList) {
			ani.move();
		}
	}
	
	public void testCasting() {
		for (int i = 0; i < aniList.size(); i++) {
			Animal2 ani = aniList.get(i);
			if (ani instanceof Human2) {
				Human2 h = (Human2)ani;
				h.readBook();
			} else if (ani instanceof Tiger2) {
				Tiger2 t = (Tiger2)ani;
				t.hunting();
			} else if (ani instanceof Eagle2) {
				Eagle2 e = (Eagle2)ani;
				e.flying();
			} else {
				System.out.println("지원되지 않는 형입니다.");
			}
		}
	}
}

  • 위 코드를 한번 살펴보겠습니다.
  • 각 동물 클래스를 인스턴스로 생성하여 Animal 형으로 선언한 배열에 추가하였습니다. (ArrayList 이용)
  • Animal 형으로 선언한 배열에 추가되는 요소의 자료형은 묵시적 형 변환에 의해서 모두 Animal 형으로 변환됩니다.
  • 이때 배열의 요소들을 통해서 호출할 수 있는 메서드는 당연스럽게도 Animal 클래스에 선언된 메서드들 뿐입니다. 
  • 향상된 for 문을 사용하여 모든 배열 요소를 하나씩 꺼내 move() 메서드를 호출하면 가상 메서드에 의하여 각 인스턴스들에서 재정의된 메서드가 호출됩니다. 
  • 그런데, 각 클래스에서 제공하는 readBook(), hunting(), flying() 메서드는 현재 배열의 요소가 모두 Animal 형이므로 사용할 수 없습니다. 
  • 다시 말해 자료형이 Animal 형인 상태에서는 Human 클래스가 제공하는 readBook( ) 메서드를 호출할 수 없습니다. 
  • 각각의 클래스에 선언된 readBook( ), hunting( ), flying( ) 을 호출하기 위해서는 다시 원래 자료형으로 다운 캐스팅되어야 합니다. 
  • instanceof 를 활용하여 실제 인스턴스 형을 살펴본 후에 다운 캐스팅을 하면 오류를 방지하면서 각 클래스에 있는 메서드를 호출할 수 있습니다.