본문 바로가기
Java/Basic

다형성(polymorphism)

by 최로이 2021. 3. 30.

1. 다형성

다형성이란 여러 가지 형태를 가질 수 있는 능력을 의미하며, 좀 더 구제적이지만 간단히 말하면 부모 클래스의 참조변수로 자식 클래스의 인스턴스를 참조하는 것이다.

 

간단히 예를 들면 서로 다른 두개의 클래스가 있다고 하면 쉽게 알 수 있다.

 

예제1)

public class Tv {
	boolean power; //전원 상태
	int channel; //채널
	
	void power(){ }
	void channelUp(){ }			//채널 up
	void channelDown(){ }	//채널 down
}

class CaptionTv extends Tv{
	
	String text; 		//캡션을 보여주기 위한 문자열
	void caption() { }
}

이때, Tv와 CaptionTv는 서로 상속관계에 있으며, 이 두 클래스의 인스턴스를 생성하고 사용하기 위해서는 다음과 같이 할 수 있다.

 

지금까지의 방법

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

 

다형성에서의 방법

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

 

위의 코드에서 CaptionTv인스턴스를 2개 생성하고, 참조변수 ct와 t가 생성된 인스턴스를 하나씩 참조하도록 했다. 이러면 실제 인스턴스가 CaptionTv타입이라 할지라도 참조변수 t로는 CaptionTv의 모든 멤버를 사용할 수 없다.

Tv타입의 참조변수로는 CaptionTv인스턴스 중에서 Tv클래스의 멤버들(상속받은 멤버 포함)만 사용할 수 있다. 따라서, 생성된 CaptionTv인스턴스의 멤버 중에서 Tv클래스에 정의되지 않은 멤버인 text와 caption()은 참조변수 t로 사용이 불가능하다. 즉, t.text 또는 t.caption()와 같이 할 수 없다. 둘 다 같은 타입의 인스턴스지만 참조변수의 타입에 따라 사용할 수 있는 멤버의 개수가 달라진다. 

 

위와 반대로 자식 타입의 참조변수로 부모 타입의 인스턴스를 참조하는 것은 불가능하다. 클래스는 상속을 통해 확장될 수는 있어도 축소될 수는 없다. 부모 인스턴스의 멤버 개수는 자식 인스턴스의 멤버 개수보다 항상 적거나 같다.

 

정리하자면 다음과 같다.

① 부모타입의 참조변수로 자식타입의 인스턴스를 참조할 수 있다.
② 자식타입의 참조변수로 부모타입의 인스턴스를 참조할 수 없다.

 

2. 다형성에서의 참조변수의 형변환

 

- 사용할 수 있는 멤버의 갯수를 조절하는 것 : 부모타입의 참조변수에 속한 멤버가 자식 타입의 멤버 갯수보다 작을 경우 자신에게 속한 멤버만 사용할 수 있다.

- 부모, 자식 관계의 참조변수마누  서로 형변환 가능(★) : 상속관계에 속한 것만 형변환이 가능하다

 

서로 상속관계에 있는 클래스사이에서만 가능하다. 예를 들어 자식타입의 참조변수를 부모타입의 참조변수로, 부모타입의 참조변수를 자식타입의 참조변수로 형변환만 가능하다.

방법은 일반적인 형변환과 동일하다. 변환하려는 참조변수 앞에 변환하려는 타입을 괄호()에 지정하여 형변환 하면 된다. 다만 자손타입의 참조변수를 조상타입의 참조변수로 변환하는 것을 업캐스팅, 그 반대를 다운캐스팅이라 한다.

자손타입 → 조상타입(Up-casting) : 형변환 생략가능
자손타입 → 조상타입(Down-casting) : 형변환 생략불가

예제를 통해 비교해보자.

 

참조변수 형변환 예제1)

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();	
		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!!!");
	}
}

예제1의 그림

1) Car car = null;

  • Car타입의 참조변수 car를 선언하고 null로 초기화.

2) FireEngine fe = new FireEngine();

  • FireEngine인스턴스를 생성하고 FireEngine타입으 참조변수가 참조하도록 함.

3) car = fe; ()

  • 참조변수 fe가 참조하고 있는 인스턴스를 참조변수 car가 참조하도록 한다. fe의 값(fe가 참조하고 있는 인스턴스의 주소)이 car에 저장된다. 
  • 참조변수 car를 통해서도 FireEngine인스턴스를 사용할 수 있지만, car는 Car타입이기 때문에 Car클래스의 멤버가 아닌 water()는 사용할 수 없다.

4) fe2 = (FireEngine)car (자식타입 ← 부모타입)

  • 참조변수 car가 참조하고 있는 인스턴스를 참조변수 fe2가 참조하도록 한다. 이 때 두 참조변수의 타입이 다르므로 참조변수 car를 형변환한다.
  • car에는 FireEngine인스턴스의 주소가 저장되어 있으므로 fe2에도  FireEngine인스턴스의 주소가 저장된다.
  • 참조변수 fe2를 통해서도 FireEngine 인스턴스의 모든 멤버들을 사용할 수 있다.

 

참조변수 형변환 예제1-2)

class CastingTest2 {
	public static void main(String args[]) {
		Car car = new Car();
		Car car2 = null;
		FireEngine fe = null;
  
		car.drive();
		fe = (FireEngine)car;		// 8번째 줄. 컴파일은 OK. 실행 시 에러가 발생. 이유는 당연히!
		fe.drive();
		car2 = fe;
		car2.drive();
	}
}

 

위를 간단하게 정리하면 Car클래스는 부모, FireEngine은 자식클래스다. 이때 부모타입의 참조변수와 자식타입의 참조변수 간에는 서로 형변환이 가능하다는 의미다. 단, 형변환을 할 때 변환하려는 참조변수 앞에  타입 기입을 생략하느냐 마느냐의 차이는 존재한다는 것이 핵심이다. 규칙은 위에서 적었던 것과 같다.

 

다형성에서의 형변환은 형변환하려는 참조변수가 형변환 하는 인스턴스보다 멤버의 개수보다 적으면 된다. 하지만 반대의 경우, 참조변수가 다룰 수 있는 멤버의 개수를 늘리는 것이기 때문에 문제가 발생할 수 있다.  결국, 참조변수가 가리키는 인스턴스의 타입이 무엇인지 확인하는 것이 중요하다.

댓글