Dev/Java

다형성(Polymorphism)과 참조변수 ( with. instanceof 연산자의 필요성)

린네의 2024. 3. 12. 00:02

 

다형성과 참조변수 

객체지향개념에서 다형성이란 '여러 가지 형태를 가질 수 있는 능력'을 의미하며 자바에서 한 타입의 참조변수로 여러 타입의 객체를 참조할 수 있도록 함으로써 다형성을 프로그램적으로 구현할 수 있다.

 

즉 조상클래스 타입의 참조변수로 자손클래스의 인스턴스를 참조할 수 있도록 하는 것이다.

 

 

Class Tv {
	boolean power;
    int channel;
    
    void power();
    void channelUp();
    void channelDonw();

}


class CaptionTv extends Tv {

	String next;
	void caption() 
    
    ...

}

 

Tv 클래스와 CaptionTv 클래스가 다음과 같이 정의 되어 있을 때 두 클래스를 생성하고 사용하는 방법은 일반적으로 다음과 같다.

 

 

Tv t = new Tv();
CaptionTv c = new CaptionTv();

 

인스턴스의 타입과 참조 변수의 타입이 일치하는 것이 보통이지만, Tv와 CaptionTv클래스가 서로 상속관계에 있을 경우 조상 클래스 타입의 참조 변수로 자손 클래스의 인스턴스를 참조하도록 하는 것도 가능하다.

 

Tv t = new CaptionTv(); // 조상 타입의 참조변수로 자손 인스턴스를 참조

 

이 경우 실제 인스턴스가 CaptionTv 타입이라고 할지라도 변수 t 로는 CaptionTv인스턴스의 모든 멤버를 사용할 수 없다.  즉 Tv클래스의 멤버들(상속받은 멤버 포함)만 사용할 수 있다.

 

코드로 따지면, CaptionTv에만 선언되어 있는 text 와 caption()는 사용할 수 없다는 것이다.

 

c와 t 모두 같은 타입의 인스턴스지만 참조 변수의 타입에 따라 사용할 수 있는 멤버의 개수가 달라지게 된다.

 

반대로 자손타입의 참조변수로 조상 타입의 인스턴스를 참조하는 것은 불가능하다. (  자식이 부모를 닮는 것은 당연하지만, 부모가 자식을 닮는다는 표현은 어색하다고 생각하면 편하다 ) 

 

CaptionTv c = new Tv(); //  컴파일 에러

 

참조변수가 사용할 수 있는 멤버의 개수는 인스턴스의 멤버 개수보다 같거나 적어야 한다.

 

만약 자손타입의 참조변수에 조상 타입의 인스턴스를 참조하려면 형변환을 해야 한다. 아래 예시 코드를 보자.

// 자손타입 -> 조상타입 : 형변환 생략가능
Car car = null;
FireEngine fe = new FireEngine();
FireEngine fe2 = null;

car = fe; // car = (Car)fe; 와 동일하지만 자손타입 -> 조상타입으로 형변환이 생략 됨 
fe2 = (FireEngine)car; // 조상타입 -> 자손타입 : 형변환 생략불가

 

여기서 자손타입 -> 조상타입의 형변환을 Up-Cating,  조상타입 -> 자손타입의 형변환을 Down-casting 이라고 한다.

Up-casting 은 생략 가능하지만 Down-casting 은 생략이 불가능하다. 

 

 

위 내용을 정리하면 다음과 같다.

 

  • 서로 상속 관게에 있는 타입 간의 형변환은 양방향으로 자유롭게 수행할 수 있다
  • 참조 변수가 가리키는 인스턴스의 자손타입으로 형변환은 허용되지 않는다. 따라서 참조변수가 가리키는 인스턴스의 타입이 무엇인지 확인하는 것이 중요하다.

 

위 내용을 잘 이해 했다면 아래 코드에서 이상함을 느껴야 한다.

 

Class CastTest {

	public static void main(String args[]) {
    
    	Car car = new Car();
        Car car2 = null;
        
        FireEngine fe = null;
        fe = (FireEngine)car;
       
        car2 = fe;

    }

}

 

 

혹시 이상함을 못느꼈다면 위에 있는 내용을 다시 정독하자. 

 

fe = (FireEngine)car 에서 발생한다. car 이 new Car()로 정의되어 있기 때문에, 조상 타입의 인스턴스를 자손타입이 참조변수로 참조하는 것이 허용되지 않음에 따라 실행 오류가 발생하는 것이다. 

 

하지만 이 코드는 컴파일 할 때 에러가 발생하지 않는다. 컴파일 시에는 참조 변수간의 타입만 체크하기 때문이다.  이러한 문제를 해결하기 위해 instance of 연산자가 필요하다.

 

 

Instanceof 연산자

 

위 내용에서 언급했듯이 조상타입의 참조변수로 자손타입의 인스턴스를 참조할 수 있기 때문에 참조변수의 타입과 인스턴스 타입이 항상 일치하지 않을 수 있다.

 

따라서 참조변수가 참조하고 있는 인스턴스의 실제 타입을 알아보기 위해 instanceof 연산자를 사용한다.

 

  • 주로 조건문에 사용
  • 왼쪽에는 참조 변수를, 오른쪽에는 타입(클래스명)을 피연산자로 위치시킨다
  • 연산의 결과로는 boolean 이 반환 된다.
void doWork(Car c) {

	if(a instanceof FireEngine) {
     		...
    }
}

 

 

실제 인스턴스와 같은 타입의 instanceof 연산 이외에 조상타입의 instanceof 연산에도 true 결과를 얻으며, instanceof 연산의 결과가 true라는 것은 검사한 타입으로 형변환을 해도 아무런 문제가 없다는 것을 뜻한다.