상속과 함께 객체지향개념의 중요한 특징 중의 하나인 다형성에 대해서 배워 보도록 하자.
다형성은 상속과 깊은 관계가 있으므로 학습하기에 앞서 상속에 대해 충분히알고 있어야 한다.
객체지향개념에서 다형성이란 '여러 가지 형태를 가질 수 있는 능력'을 의미하며, 자바에서는 한 타입의 참조변수로 여러 타입의 객체를 참조할 수 있도록 함으로써 다형성을 프로그램적으로 구현하였다.
이를 좀 더 구체적으로 말하자면, 조상클래스 타입의 참조변수로 자손클래스의 인스턴스를 참조할 수 있도록 하였다는 것이다.
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인스턴스를 다루기 위해서는 Tv타입의 참조변수를 사용하고, CaptionTv인스턴스를 다루기 위해서는 CaptionTv타입의 참조변수를 사용했다.
이처럼 인스턴스의 타입과 참조변수의 타입이 일치하는 것이 보통이지만, Tv와 CaptionTv클래스가 서로 상속관계에 있을 경우, 다음과 같이 조상 클래스 타입의 참조변수로 자손 클래스의 인스턴스를 참조하도록 하는 것도 가능하다.
Tv t = new CaptionTv(); // 조상 타입의 참조변수로 자손 인스턴스를 참조
그렇다면 이제 인스턴스를 같은 타입의 참조변수로 참조하는 것과 조상타입의 참조변수로 참조하는 것은 어떤 차이가 있는지에 대해서 알아보도록 하자.
Tv t = new CaptionTv();
CaptionTv c = new CaptionTv();
t.text = "tt"; // 이건 안됨.
c.text = "tt"; // 이건 됨.
위의 코드에서 CaptionTv 인스턴스 2개를 생성하고, 참조변수 c와 t가 생성된 인스턴스를 하나씩 참조하도록 하였다. 이 경우 실제 인스턴스가 CaptionTv타입이라 할지라도, 참조변수 t로는 CaptionTv 인스턴스의 모든 멤버를 사용할 수 없다.
Tv타입의 참조변수로는 CaptionTv 인스턴스 중에서 Tv클래스의 멤버들(상속받은 멤버 포함)만 사용할 수 있다. 따라서, 생성된 CaptionTv 인스턴스의 멤버 중에서 Tv클래스에 정의 되지 않은 멤버, text와 caption()은 참조변수 t로 사용이 불가능하다. 즉, t.text 또는 t.caption()와 같이 할 수 없다는 것이다. 둘 다 같은 타입의 인스턴스지만 참조변수의 타입에 따라 사용할 수 있는 멤버의 개수가 달라진다.
반대로 자손타입의 참조변수로 조상타입의 인스턴스를 참조하는 것은 불가능 하다.
CaptionTv c = new Tv();
그 이유는 실제 인스턴스인 Tv의 멤버 개수보다 참조변수 c가 사용할 수 있는 멤버 개수가 더 많기 때문이다. 그래서 이를 허용하지 않는다.
그래서 참조변수가 사용할 수 있는 멤버의 개수는 인스턴스의 멤버 개수보다 같거나 적어야 한다.
public class Wrapping {
public static void main(String[] args) {
Box box1 = new Box();
PaperBox box2 = new PaperBox();
GoldPaperBox box3 = new GoldPaperBox();
wrapBox(box1); // Gold Wrapping
wrapBox(box2); // Paper Wrapping
wrapBox(box3); // Simple Wrapping
checkBox(box1); // 박스가 맞습니다.
checkBox(box2); // 박스가 맞습니다.
checkBox(box3); // 박스가 맞습니다.
}
private static void wrapBox(Box box) { // Upcasting
if (box instanceof GoldPaperBox) {
((GoldPaperBox) box).goldPaperWrap(); // Downcasting
} else if (box instanceof PaperBox) {
((PaperBox) box).paperWrap(); // Downcasting
} else if (box instanceof Box) {
box.simpleWrap();
}
}
static void checkBox(Box b) { // Upcasting
// 이게 박스인지 아닌지 확인 하는곳.
if (b instanceof Box) {
System.out.println("박스가 맞습니다.");
}
}
또 다른 예
package sec08;
public class MyFriends {
static int ucnt = 0;
// 메인 메서드
public static void main(String[] args) {
// 부모 타입의 객체 배열 생성
Friend[] frns = new Friend[10];
// Upcasting해서 배열의 객체 인스턴스 생성
frns[ucnt++] = new UnivFriend("Kim", "Computer Sience", "010-1234-5678");
frns[ucnt++] = new UnivFriend("Park", "Computer Sience", "010-1234-5678");
frns[ucnt++] = new UnivFriend("Jang", "Teacher", "010-1234-5678");
frns[ucnt++] = new UnivFriend("Choi", "Computer Sience", "010-1234-5678");
frns[ucnt++] = new UnivFriend("Lee", "Hotel Management", "010-1234-5678");
frns[ucnt++] = new CompFriend("Cha", "R&D", "010-1212-4545");
frns[ucnt++] = new CompFriend("Lim", "Sales", "010-2121-5454");
frns[ucnt++] = new CompFriend("Kim", "Buyer", "010-7878-9465");
frns[ucnt++] = new CompFriend("Kim", "CEO", "010-2580-8520");
frns[ucnt++] = new CompFriend("Han", "Electric", "010-7894-5612");
print(frns);
}
static void print(Friend[] frns) {
for (int i = 0; i < ucnt - 1; i++) {
frns[i].showInfo();
System.out.println();
}
}
}
// 부모 클래스 생성
package sec08;
public class Friend {
private String name;
private String phone;
public Friend(String name, String phone) {
this.name = name;
this.phone = phone;
}
public void showInfo() {
System.out.println("이름 : " + name);
System.out.println("전화번호 : " + phone);
}
}
// 자손 클래스 생성
package sec08;
public class UnivFriend extends Friend {
private String major;
public UnivFriend(String name, String major, String phone) {
super(name, phone);
// 이렇게 getter로 접근하여 가져올수도 있음.
// this.name = super.getName();
// this.major = super.getDescription();
// this.phone = super.getPhone();
this.major = major;
}
// Override 사용
@Override
public void showInfo() {
super.showInfo();
System.out.println("전공 : " + major);
}
}
// 자손 클래스 생성
package sec08;
public class CompFriend extends Friend {
private String department;
public CompFriend(String name, String department, String phone) {
super(name, phone);
this.department = department;
}
// Override 사용
@Override
public void showInfo() {
super.showInfo();
System.out.println("부서 : " + department);
}
}