(polymorphism)
다형성이란, '여러 가지 형태를 가질 수 있는 능력' 을 의미함. 한 타입의 참조변수로 여러 타입의 객체를 참조할 수 있도록 함으로써 다형성을 프로그램적으로 구현함. 조상클래스 타입의 참조변수로 자손클래스의 인스턴스를 참조할 수 있음.
class Tv {
boolean power;
int channel;
void power() { power = !power; }
void channelUp() { ++channel; }
void channelDown() { --channel; }
}
class SmartTv extends Tv {
String text; // 캡션(자막)을 보여 주기 위한 문자열
void caption() { /* 내용생략 */ }
Tv t = new Tv();
SmartTv s = new SmartTv();
위와 같이 인스턴스를 다루기 위해서, 인스턴스의 타입과 일치하는 타입의 참조변수를 사용함. 그러나 Tv와 SmartTv클래스와 같이 서로 상속관계에 있는 경우, 조상 클래스 타입의 참조변수로 자손 클래스의 인스턴스를 참조하는 것이 가능함.
Tv t = new SmartTv();
// 타입 불일치. 조상 타입의 참조변수로 자손 인스턴스 참조
SmartTv s = new SmartTv();
// 참조변수와 인스턴스 타입 일치
Tv t = new SmartTv();
// 조상 타입 참조변수로 자손 타입 인스턴스 참조
SmartTv s = new Tv();
// 에러!! 허용 안됨.
👉 둘 다 같은 타입의 인스턴스지만, 참조변수의 타입에 따라 사용할 수 있는 멤버의 개수가 달라짐.
기본형 변수처럼 참조변수도 형변환이 가능함. 단, 서로 상속관계에 있는 클래스 사이에서만 가능하기 때문에 자손타입의 참조변수를 조상타입의 참조변수로, 조상타입의 참조변수를 자손타입의 참조변수로의 형변환만 가능함. 바로 윗 조상이나 자손이 아닌, 조상의 조상으로도 형변환이 가능함. 따라서, 모든 참조변수는 모든 클래스의 조상인 Object 클래스 타입으로 형변환이 가능함.
class Car { }
class FireEngine extends Car { }
class Ambulance extends Car { }
FireEngine f = new FireEngine();
Car c = (Car)f;
// OK 조상인 Car타입으로 형변환(생략가능)
FireEngine f2 = (FireEngine)c;
// OK 자손인 FireEngine타입으로 형변환(생략불가)
Ambulance a = (Ambulance)f;
// 에러!! 상속관계가 아닌 클래스 간의 형변환 불가!
기본형의 형변환과 달리 참조형의 형변환은 변수에 저장된 값(주소값)이 변환되는 것이 아니라, 그저 참조변수를 다른 종류로 바꾸는 것임. 참조변수의 타입을 바꾸는 이유는 사용할 수 있는 멤버 개수를 조절하기 위한 것임.
조상타입으로 형변환을 생략할 수 있는 이유
는 조상타입으로 형변환을 하면 다룰 수 있는 멤버의 개수가 줄어들기 때문에 항상 안전하기 때문임. 반대로 실제 객체가 가진 기능보다 참조변수의 기능이 더 많으면 안됨.
instanceof
연산자참조변수
가 참조하고 있는 인스턴스의 실제 타입
을 알아보기 위해 instanceof 연산자
를 사용함. 주로 조건문
에 사용되며, instanceof 의 왼쪽
에는 참조변수
를, 오른쪽
에는 타입(클래스명)
이 피연산자로 위치함. 그리고 연산의 결과
로 boolean 값
인 true와 false 중의 하나를 반환함.
instanceof
를 이용한 연산결과로 true를 얻었다는 것은 참조변수가 검사한 타입으로 형변환이 가능하다는 것을 의미함.
값이 null
인 참조변수에 대해 instanceof 연산
을 수행하면 false
를 결과로 얻음.
void doWork(Car c) {
if (c instanceof FireEngine) { // 1. 형변환이 가능한지 확인
FireEngine fe = (FireEngine)c; // 2. 형변환
fe.water();
...
참조변수의 다형적인 특징은 메서드의 매개변수에도 적용됨. 참조형 매개변수
는 메서드 호출시, 자신과 같은 타입 또는 자손 타입의 인스턴스를 넘겨줄 수 있음.
class Product {
int price;
int bonusPoint;
}
class Tv extends Product { }
class Computer extends Product { }
class Buyer {
int money = 1000;
int bonusPoint = 0;
}
위의 코드에서 Buyer 클래스
에 물건을 구입하는 기능의 메서드를 추가함.
void buy(Tv t) {
// Buyer가 가진 돈(money)에서 제품의 가격(t.price)만큼 뺀다.
money = money - t.price;
// Buyer의 보너스점수에 제품의 보너스점수를 더한다.
bonusPont = bonusPoint + t.bonusPoint;
}
buy(Tv t)
로는 Tv밖에 살 수 없기 때문에 아래와 같이 다른 제품들도 구입할 수 있는 메서드를 추가함.
void buy(Computer c) {
money = money - c.price;
bonusPoint = bonusPoint + c.bonusPoint;
}
이렇게 되면, 제품의 종류가 늘어날 때마다 Buyer 클래스
에는 새로운 buy 메서드
를 추가해주어야 함. 그러나 메서드의 매개변수에 다형성을 적용하면 하나의 메서드로 간단히 처리할 수 있음.
void buy(Product p) {
money -= p.price;
bonusPoint += p.bonusPoint;
}
위의 코드처럼 Product 타입의 참조변수
로 처리하면, 메서드의 매개변수로 Product 클래스의 자손타입의 참조변수
면 어느 것이나 매개변수로 받아들일 수 있음. 다른 제품을 추가할 때 Product 클래스
를 상속받기만 하면 됨.
Buyer b = new Buyer();
Tv t = new Tv();
Computer c = new Computer();
b.buy(t);
b.buy(c);
// Tv t = new Tv();
// b.buy(t);
// 한 문장으로 줄이면 b.buy(new Tv());