본문 바로가기

JavaScript

[JAVA] 다형성(polimorphism)에 대해서

1. 다형성이란?


객체지향 프로그래밍의 3요소(캡슐화, 상속, 다형성) 중 하나인 다형성에 대해서 알아보자. 

 

다형성이란 '여러 가지 형태를 가질 수 있는 능력'을 의미한다. 자바에서는 한 타입의 참조변수로 여러 타입의 객체를 참조할 수 있도록 함으로써 다형성을 프로그램적으로 구현하였다. 이를 좀 더 구체적으로 말하자면, 조상 클래스 타임의 참조변수로 자손 클래스의 인스턴스를 참조할 수 있도록 하였다는 것이다. 

 

Class Tv {
	boolean power;
	int channel;
    
	void power() {power=!power;}
	void channelUp() {++channel;}
	void channelDown() {--channel;}
}
Class CaptionTv extends Tv {
	String text;
	void caption() {
		System.out.println("text");
	}
}

위와 같이 Tv클래스와 Tv클래스를 상속받은 CaptionTv클래스를 정의하였다. 두 클래스를 사용하기 위해서는 다음과 같이 인스턴스를 생성했을 것이다. 

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

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

Tv t = new CaptionTv();

이때, 참조변수 t로 CaptionTv인스턴스를 참조하고 있다 하더라도 오로지 Tv클래스의 멤버들만 사용할 수 있다. CaptionTv의 text와 caption()은 참조변수 t로 사용이 불가능하다. 

 

반대로 다음과 같이 자손타입의 참조변수로 조상 타입의 인스턴스를 참조하는 것은 불가능하다. 

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

그 이유는 실제 인스턴스인 Tv의 멤버 개수보다 참조변수 c가 사용할 수 있는 멤버 개수가 더 많기 때문이다. 참조변수가 사용할 수 있는 멤버의 개수는 인스턴스의 멤버 개수보다 같거나 적어야 한다. 


두줄 요약

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

 

2. 참조변수의 형변환


서로 상속관계에 있는 클래스 사이에서는 형변환이 가능하다. 자손타입의 참조변수로 조상 타입의 인스턴스를 참조하는 것은 불가능하기 때문에 형변환을 생략할 수 없다. 

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

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

public class Car {
	String color;
	int door;
	void drive() {
		System.out.println("붕붕~");
	}
}
public class FireEngine extends Car {
	void water() {
		System.out.println("물뿌리기!!!");
	}
}
public class Ambulance extends Car {
	void siren() {
		System.out.println("삐뽀~삐뽀~");
	}
}

클래스 간의 상속 관계

public static void main(String[] args) {
	FireEngine f;
	Ambulance a;
	Car c;
	a = (Ambulance)f; // 상속관계가 아닌 클래스 간의 형변환 불가
	c = f; // 업캐스팅. 형변환 생략
	f = (FireEngine)c; // 다운캐스팅. 형변환 생략 불가
}

형변환이 가능한지는 서로 상속 관계에 있는지 따져봐야 하기 때문에 형변환을 수행하기 전에 instanceof 연산자를 사용해서 참조변수가 참조하고 있는 실제 인스턴스의 타입을 확인하는 것이 안전하다. 

 

3. instanceof 연산자


instanceof연산자는 참조변수가 참조하고 있는 인스턴스의 실제 타입을 알아보기 위해 사용한다. 주로 조건문에 사용되며, instanceof의 왼쪽에는 참조변수를, 오른쪽에는 타입(클래스명)이 피연산자로 위치한다.

 

연산 결과로 true, false를 반환한다. 

 

true => 형변환 가능

false => 형변환 불가

 

4. 그렇다면 왜 이렇게 다른 타입으로 인스턴스를 생성할까?


public class Product {
	int price;
	int bonusPoint;
	
	Product(int price) {
		this.price = price;
		bonusPoint = (int)(price/10.0);
	}
}
public class Tv extends Product {
	Tv() {
		super(100);
	}
	@Override
	public String toString() {
		return "Tv";
	}
}
public class Computer extends Product {
	Computer() {
		super(200);
	}
	@Override
	public String toString() {
		return "Computer";
	}
}
public class Buyer {
	int money = 1000;
	int bonusPoint = 0;
	
	void buy(Product p) {
		money -= p.price;
		bonusPoint += p.bonusPoint;
		System.out.println(p + "을/를 구입하셨습니다.");
	}
}
public class PolyArgumentTest {

	public static void main(String[] args) {
		Buyer b = new Buyer();
		
		b.buy(new Tv()); // Tv을/를 구입하셨습니다.
		b.buy(new Computer()); Computer을/를 구입하셨습니다.
		
		System.out.println("현재 남은 돈은 " + b.money + "만원입니다."); // 현재 남은 돈은 700만원입니다.
		System.out.println("현재 보너스점수는 " + b.bonusPoint + "점입니다."); // Tv을/를 구입하셨습니다.
	}

}

buyer클래스에 buy메서드를 buy(Conputer c) 혹은 buy(Tv t)으로 만든다면, 제품이 늘어날 때마다 새로운 메서드를 오버로딩하여 정의해야 할 것이다.

 

그러나 위의 예제처럼 모든 제품의 조상클래스인 Product를 파라미터로 받는다면 자손타입의 참조변수를 받아들일 수 있어 훨씬 효율적인 코드가 된다. Product타입의 객체로 Computer타입, Tv타입을 참조할 수 있는 다형성의 성질을 이용한 것이다.