다형성(Polymorphism)과 참조변수 ( with. instanceof 연산자의 필요성)
다형성과 참조변수
객체지향개념에서 다형성이란 '여러 가지 형태를 가질 수 있는 능력'을 의미하며 자바에서 한 타입의 참조변수로 여러 타입의 객체를 참조할 수 있도록 함으로써 다형성을 프로그램적으로 구현할 수 있다.
즉 조상클래스 타입의 참조변수로 자손클래스의 인스턴스를 참조할 수 있도록 하는 것이다.
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라는 것은 검사한 타입으로 형변환을 해도 아무런 문제가 없다는 것을 뜻한다.