본문 바로가기

JAVA/자바의정석

객체지향 프로그래밍 II (4) - 다형성

728x90
반응형

다형성(polymorphism)

[1] 다형성이란?

객체 지향 개념에서 다형성이란 '여러 가지 형태를 가질 수 있는 능력'을 의미한다.

자바에서는 한 타입의 참조변수로 여러 타입의 객체를 참조할 수 있도록 함으로써 다형성을 프로그램적으로 구현하였다.

구체적으로 말하면, 조상클래스 타입의 참조변수로 자손클래스의 인스턴스를 참조할 수 있도록 하였다는 것이다.

class Tv{
	boolean power;
    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 클래스가 서로 상속관계에 있을 경우, 조상 클래스 타입의 참조변수로 자손 클래스의 인스턴스를 참조하도록 하는 것도 가능하다.

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

그렇다면 인스턴스를 같은 타입의 참조변수로 참조하는 것과 조상타입의 참조변수로 참조하는 것은 어떤 차이가 있는지에 대해서 알아보도록 하자.

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

Tv 타입의 참조변수로는 CaptionTv 인스턴스 중에서 Tv클래스의 멤버들(상속받은 멤버포함)만 사용할 수 있다.

따라서, 생성된 CaptionTv 인스턴스의 멤버 중에서 Tv클래스에 정의되지 않은 멤버(text와 caption() )은 참조변수 t로 사용이 불가능하다.

 

자손타입의 참조변수로 조상타입의 인스턴스를 참조하는 것은 에러가 발생한다

CaptionTv c = new Tv();		//에러 발생!

 

조상타입의 참조변수로 자손타입의 인스턴스를 참조할 수 있다.
반대로 자손타입의 참조변수로 조상타입의 인스턴스를 참조할 수는 없다.

[2] 참조변수의 형변환

기본형 변수와 같이 참조변수도 형변환이 가능하다. 단, 서로 상속관계에 있는 클래스 사이에서만 가능하기 때문에

자손타입 참조변수 <-> 조상타입 참조변수 로의 형변환만 가능하다.

 

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

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

 

참조변수간의 형변환 역시 캐스트 연산자를 사용하며, 괄호( ) 안에 변환하고자 하는 타입의 이름(클래스명)을 적어주면 된다.

class Car {
	String color;
	int door;
	void drive() {
		System.out.println("Brr~");
	}
	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");
	}
}

위와 같이 세 클래스 Car, FireEngine, Ambulance가 정의되어 있을때

세클래스 간의 관계는 위 그림과 같다.

Car 클래스와 FireEngine 클래스 , Car 클래스와 Ambulance 클래스 끼리는 서로 형변환이 가능하지만

FireEngine클래스와 Ambulance 클래스의 참조변수 간에는 서로 형변환이 불가능하다.

 

형변환은 참조변수의 타입을 변환하는 것이지 인스턴스를 변화하는 것은 아니기 때문에 참조변수의 형변환은 인스턴스에 아무런영향을 미치지 못한다.

단지 참조변수의 형 변환을통해서, 참조하고 있는 인스턴스에서 사용할 수 있는 멤버의 범위(개수)를 저절하는 것 뿐이다.

 

 

[3] instanceof 연산자

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

주로 조건문에 사용되며 instanceof 왼쪽에는 참조변수를, 오른쪽에는 타입(클래스명)을 입력한다.

instanceof를 이용한 연산결과로 true 가 나온다면 참조변수가 검사한 타입을 형변환이 가능하다는 것을 의미한다.

 

public class Page345 {
	public static void main(String[] args) {
		FireEngine fe = new FireEngine();
		Ambulance am = new Ambulance();
		
		if (fe instanceof FireEngine) {
			System.out.println("This is FireEngine instance");
		}
		if (fe instanceof Car) {
			System.out.println("This is Car instance");
		}
		if (fe instanceof Object) {
			System.out.println("This is an Object instance");
		}
		System.out.println(fe.getClass().getName()); // 클래스 이름 출력
	}
}

출력결과

This is FireEngine instance
This is Car instance
This is an Object instance
com.java.unit2.FireEngine

생성된 인스턴스는 FireEngine 타입인데도 , Object타입과 Car 타입의 instanceof연산에서 true를 얻었다.

 그 이유는 FireEngine클래스가 Object와 Car클래스의 자손 클래스이므로 조상의 멤버들을 상속받았기 때문이다.

요약하면,

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

[4] 참조변수와 인스턴스의 연결

멤버변수가 조상 클래스와 자손 클래스에 중복으로 정의된 경우, 조상타입의 참조변수를 사용했을 때는 조상 클래스에 선언된 멤버변수가 사용되고, 자손타입의 참조변수를 사용했을 때는 자손 클래스에 선언된 멤버변수가 사용된다.

하지만, 중복 정의되지 않은 경우, 조상타입의 참조변수를 사용했을 때와 자손타입의 참조변수를 사용했을 때의 차이는 없다.

 

[5] 매개변수의 다형성

참조변수의 다형적인 특징은 메서드의 매개변수에도 적용된다.

아래와 같이 코드가 정의되어 있을때,

class Product{
	int price;
	int bonusPoint;
}
class Tv extends Product{}
class Computer extends Product{}
class Audio extends Product{}

class Buyer{
	int money = 1000;
	int bonusPoint = 0;
    
    void buy(Tv t) {
		money = money - t.price;
		bonusPoint = bonusPoint + t.bonusPoint;
	}
	void buy(Computer c) {
		money = money - c.price;
		bonusPoint = bonusPoint + c.bonusPoint;
	}
	void buy(Audio a) {
		money = money - a.price;
		bonusPoint = bonusPoint + a.bonusPoint;
	}
}

이런식으로 코드르 짜게되면, 제품의 종류가 늘어날 때마다 Buyer 클래스에는 새로운 buy 메서드를 추가해 주어야 할것이다.

그러나 메서드 매개변수에 다형성을 적용하면 하나의 메서드로 간단히 처리할 수 있다.

	void buy(Product p) {
		money = money - p.price;
		bonusPoint = bonusPoint + p.bonusPoint;
	}

다른 제품 클래스를 추가할 때 Product클래스를 상속받기만 하면, buy(Product p) 메서드의 매개변수로 받아들여질 수 있다.

 

[6] 여러 종류의 객체를 배열로 다루기

	Product p1 = new Tv();
	Product p2 = new Computer();
	Product p3 = new Audio();

위의 코드를 Product타입의 참조변수 배열로 처리하면 아래와 같다.

	Product p[] = new Product[3];
	p[0] = new Tv();
	p[1] = new Computer();
	p[2] = new Audio();

 

Vector 클래스는 내부적으로 Object 타입의 배열을 가지고 있다.

배열에 객체를 추가하거나 제거할 수 있는 기능을 가지고 있다.

그리고 배열의 크기를 알아서 관리해주기 때문에 저장할 인스턴스의 개수에 신경 쓰지 않아도 된다.

 

728x90
반응형