객체지향개념에서 다형성이란 '여러 가지 형태를 가질 수 있는 능력'이다.
조상클래스 타입의 참조변수로 자손클래스의 인스터스를 참조할 수 있도록 하였다.
class Tv{
boolean power; //전원상태 (on / off )
int channel; //채널
void power() { power =! power; }
void channelUp() { ++channel; }
void channelDown() { -- channel; }
}
class CaptionTv extends TV {
String text; // 캡션을 보여 주기 위한 문자열
void caption() { //내용생략 }
}
클래스 Tv와 CaptionTv는 서로 상속관계에 있으며, 이 두 클래스의 인스턴스를 생성하고 사용하기 위해서는 다음과 같이 할 수 있다.
Tv t = new Tv();
CaptionTv c = new CaptionTv();
이처럼 인스턴스의 타입과 참조변수의 타입이 일치하는 것이 보통이지만, Tv와 CaptionTv클래스가 서로 상속관계에 있을 경우, 다음과 같이 조상 클래스 타입의 참조변수로 자손 클래스의 인스턴스를 참조하도록 하는 것도 가능하다.
CaptionTv c = new CaptionTv();
Tv t = new CaptionTv();
반대로 자손타입의 참조변수로 조상타입의 인스턴스를 참조하는 것은 불가능하다. 참조변수가 사용할 수 있는 멤버의 개수는 인스턴스의 멤버 개수보다 같거나 적어야 한다.
조상타입의 참조변수로 자손타입의 인스턴스를 참조할 수 있다.
반대로 자손타입의 참조변수로 조상타입의 인스턴스를 참조할 수는 없다.
서로 상속관계에 있는 클래스사이에서만 가능하기 때문에 자손타입의 참조변수를 조상타입의 참조변수로, 조상타입의 참조변수를 자손타입의 참조변수로의 형변환만 가능하다.
자손타입 --> 조상타입(Up-casting) : 형변환 생략가능
자손타입 <-- 조상타입(Down-casting) : 형변환 생략불가
class Car{
String color;
int door;
void drive() { //운전하는 기능
System.out.println("drive, Brrr~");
}
void stop() { //멈추는 기능
System.out.println("stop!!!");
}
}
class FireEngine extends Car{ //소방차
void water(){
System.out.println("water!!!");
}
}
class Ambulance extends Car{ //엠불런스
void siren(){
System.out.println("siren~~~");
}
}
자바에서는 조상과 자식관계만 존재하기 때문에 FireEngine과 Ambulance클래스는 서로 아무런 관계가 없다.
따라서 Car타입의 참조벼수와 FireEngine타입의 참조변수 그리고 Car타입의 참조변수와 Ambulance타입의 참조변수 간에는 서로 형변환이 가능하지 않다.
FireEngine f;
Ambulance a;
a = (Ambulance)f; //에러! 상속관계가 아닌 클래스간의 형변환 불가
f = FireEngine)a; //에러! 상속관계가 아닌 클래스간의 형변환 불가
Car타입 참조변수와 FireEngine타입 참조변수 간의 형변환 예)
Car car = null;
FireEngine fe = new FireEngine();
FireEngine fe2 = null;
car = fe;
fe2 = (FireEngine)car
Car타입의 참조변수 c를 Car타입의 조상인 Object타입의 참조변수로 형변환 하는 것은 참조변수가 다룰 수 있는 멤버의 개수가 실제 인스턴스가 갖고 있는 멤버의 개수보다 적을 것이 분명하므로 문제가 되지 않는다. 그래서 형 변환을 생략할 수 있도록 한 것이다.
형변환은 참조변수의 타입을 변환하는 것이지 인스턴스를 변환하는 것은 아니기 때문에 참조변수의 형변환은 인스턴스에 아무런 영향을 미치지 않는다.
단지 참조변수의 형변환을 통해서, 참조하고 있는 인스턴스에서 사용할 수 있는 멤버의 범위(개수)를 조절하는 것 뿐이다.
Tv tv = new Caption();
//위와 동일한 코드
// 둘로 분해해서 작성한 것.
CaptionTv c = new CaptionTv();
Tv t = (Tv)c;
class CastingTest1{
public static void main (String args[])
{
Car car = null;
FireEngine fe = new FireEngine();
fIREEngine fe2 = null;
fe.water();
car = fe; //car = (Car)fe;에서 형변환이 생략된 형태다.
//car.water(); //컴파일에러 Car타입의 참조변수로는 water를 호출할 수 없다.
fe2 = (FireEngine)car; //자손타입 <- 조상타입
fe2.water();
}
}
class Car{
String color;
int door;
void drive(){ //운전하는 기능
//System.out.println("drive, Brrrr~");
}
void stop(){ //멈추는기능
//System.out.println("stop!!!");
}
}
class FireEngine extends Car{ //소방차
void water(){ //물을 뿌리는 기능.
System.out.println("water!!!");
}
}
실행결과
water!!!
water!!!
실행과정
Car car = null
Car타입의 참조변수 car를 선언하고 null로 초기화한다.
FireEngine fe = new FireEngine();
FireEngine인스턴스를 생성하고 FireEngine타입의 참조변수가 참조하도록 한다.
car = fe; //조상타입 <- 자손타입
참조변수 fe가 참조하고 있는 인스턴스를 참조변수 car가 참조하도록 한다. fe값이 car에 저장된다. 이 때 두 참조변수의 타입이 다르므로 참조변서 fe가 형변환되어야 하지만 생략되었다.
이제는 참조변수 car를 통해서도 FireEngine인스턴스를 사용할 수 있지만, fe와 달리 car는 Car타입이므로 Car클래스의 멤버가 아닌 water()는 사용할 수 없다.
fe2 = (FireEngine)car; //자손타입<-조상타입
참조변수 car가 참조하고 있는 인스턴스를 참조변수 fe2가 참조하도록 한다. 이 때 두 참조변수의 타입이 다르므로 참조변수 car를 형변환하였다. car에는 FireEngine인스턴스의 주소가 저장되어 있으므로 fe2에도 FireEngine인스턴스의 주소가 저장된다.
이제는 참조변수 fe2를 통해서도 FireEngine인스턴스를 사용할 수 있지만, car와 달리, fe2는 FireEngine타입이므로 FireEngine인스턴스의 모든 멤버들을 사용할 수 있다.
class CastringTest2{
public static void main(String args[])
{
Car car = new Car();
Car car2 = null;
FireEngine fe = null;
car.drive();
fe = (FireEngine)car; //컴파일은ok, 실행 시 에러 발생
fe.drive();
car2 = fe;
car2.drive();
}
}
에러가 발생한 이유는 형변환에 오류가 있기 때문이다. 참조변수 car가 참조하고 있는 인스턴스가 Car타입의 인스턴스라는게 문제다. 조상타입의 인스턴스를 자손타입의 참조변수로 참조하는 것은 허용되지 않는다.
컴파일 시에는 참조변수간의 타입만 체크하기 때문에 실행 시 생성될 인스턴스의 타입에 대해서는 전혀 알지 못한다. 그래서 컴파일 시에는 문제가 없었지만, 실행 시에는 에러가 발생하여 실행이 비정상적으로 종료된 것이다.
- 서로 상속관계에 있는 타입간의 형변환은 양방향으로 자유롭게 수행될 수 있다.
- 참조변수가 가리키는 인스턴스의 자손타입으로 형변환은 허용되지 않는다.
- 그래서 참조변수가 가리키는 인스턴스의 타입이 무엇인지 확인하는 것이 중요하다.
instanceof를 이용한 연산결과로 true를 얻었다는 것은 참조변수가 검사한 타입으로 형변환이 가능하다는 것을 뜻한다.
void doWork (Car c){
if (c instanceof FireEngine)
{
FireEngine fe = FireEngine)c;
fe.water();
} else if ( c interfaceof Ambulance)
{
Ambulance a = (Ambulance) c;
a.siren();
}
}
이 메서드가 호출될 때, 매개변수로 Car클래스 또는 그 자손 클래스의 인스턴스르 ㄹ넘겨받겠지만 메서드 내에서는 정확히 어떤 인스턴스인지 알 길이 없다. 그래서 instanceof 연산자를 통해서 참조변수 c가 가리키고 있는 인스턴스의 타입을 체크하고, 적절히 형변환한 다음에 작업해야한다.
조상타입의 참조변수로는 실제 인스턴스의 멤버들을 모두 사용할 수 없기 때문에, 실제 인스턴스와 같은 타입의 참조변수로 형변환을 해야만 인스턴스의 모든 멤버들을 사용할 수 있다.
class InstanceofTest{
public static void main(String args []){
FireEngine fe = new FireEngine();
if(fe instanceof FireEngine)
{
System.out.println("This is a FireEngine instance.");
}
if (fe instanceof Car)
{
System.our.println("This is a Car instanceof.");
}
if (fe instanceof Object)
{
System.out.println("This is an Object instance.");
}
System.out.println(fe.getClass().getName()); //클래스의 이름을 출력
}
} //class
class FireEngine extends Car {}
실행결과
This is a FireEngine instance
This is a Car instance
This is an Object instance
FireEngine
어떤 타입에 대한 instanceof연산의 결과가 true라는 것은 검사한 타입으로 형변환이 가능하다는 것을 뜻한다.