다형성은 다양한 형태 또는 특성을 갖는다는 의미이다.
Java 객체 지향 프로그래밍에서는 부모클래스를 상속 받는 자식클래스의 인스턴스가 부모의 객체로도 사용되고, 자식클래스의 객체로도 사용될 수 있는 다양한 상황을 의미한다.
다형성은 상속(extends)을 통해서 구현이 가능하다.
아래 코드를 보면 부모클래스 Bird가 있고, 이를 상속 받는 자식클래스 Parrot이 있다.
class Bird {
String name = "새";
void method1() { System.out.printf("%s이다.%n", name); }
void bird_method() { System.out.println("부모 클래스 Bird"); }
}
class Parrot extends Bird {
String name = "앵무새";
String bird_name = "앵무새";
void parrot_method() { System.out.println("자식 클래스 Parrot"); }
@Override
void method1() {
System.out.println(System.out.printf("새의 종류는 %s이다.%n", bird_name));
}
}
두 클래스를 활용하여 네 가지 방법으로 객체를 생성해보자.
Parrot 타입의 객체 parrot1은 너무나 당연하게도 상속 받은 부모클래스의 자원과 본인(자식클래스)의 자원 모두 이용이 가능하다.
Parrot parrot1 = new Parrot();
Parrot parrot1 = new Parrot(); System.out.println(parrot1.name); System.out.println(parrot1.bird_name); parrot1.method1(); parrot1.parrot_method(); parrot1.bird_method(); ----------------------------------------- > 앵무새 > 앵무새 > 새의 종류는 앵무새이다. > 자식 클래스 Parrot > 부모 클래스 Bird
이때 상속 받은 메서드 method1과 String 변수 name의 경우 자식클래스에서 오버라이드한 형태로 출력되는 것을 알 수 있다.
그렇다면 Parrot클래스에서는 method1의 원본 "새이다."를 출력할 수 없는 걸까?
super 키워드를 사용하면 자식클래스에서도 원본 메서드의 이용이 가능하다.
class Bird {
String name = "새";
void method1() { System.out.printf("%s이다.%n", this.name); }
...
}
class Parrot extends Bird {
...
void method2() {
super.method1();
System.out.println(super.name);
}
}
public class Main {
public static void main(String[] args) {
Parrot parrot = new Parrot();
parrot1.method2();
}
}
-----------------------------------------------------------------
>> 새이다.
>> 새
Parrot클래스에 부모클래스 Bird의 메서드 method1과 변수 name을 super키워드로 받은 후, 이들을 출력하는 메서드 method2를 만들어주었다. super 키워드로 받은 값은 부모클래스의 원본 값 그대로 출력됨을 확인할 수 있다.
너무나 당연하게도 Bird 타입의 객체 bird1도 Bird클래스의 자원을 이용할 수 있다.
그리고 또 너무나 당연하게도 자식 클래스의 자원은 이용할 수 없다.
Bird bird1 = new Bird();
Bird bird1 = new Bird(); System.out.println(bird1.name); bird1.method1(); bird1.bird_method(); ------------------------------------ > 새 > 새이다. > 부모 클래스 Bird
그렇다면 타입은 부모클래스(Bird)이되, 자식클래스(Parrot)로 bird2객체를 생성해보자.
bird2의 타입이 부모클래스이기 때문에 부모클래스, Bird의 자원은 사용이 가능하다.
Bird bird2 = new Parrot();
Bird bird2 = new Parrot(); System.out.println(bird2.name); // System.out.println(bird2.bird_name); >> Error! 자식클래스의 자원 사용 못함 bird2.method1(); bird2.bird_method(); // bird2.parrot_method(); >> Error! 자식클래스의 자원 사용 못함 ------------------------------------ > 새 > 새의 종류는 앵무새이다. > 부모 클래스 Bird
여기서 두 가지 흥미로운 점을 볼 수 있다.
정말 부모클래스 타입 + 자식클래스로 생성 의 경우에는 오버라이드된 자식클래스의 메서드외에는 부모클래스의 자원만 사용 가능한 것일까?
bird2의 타입을 자식클래스(Parrot)로 강제 형 변환을 하면 자식클래스의 자원에도 접근이 가능하다!
((Parrot)bird2).parrot_method();
System.out.println(((Parrot) bird2).bird_name);
------------------------------------------------
>> 자식 클래스 Parrot
>> 앵무새
앞서 본 객체 생성 방식[3]과 반대로
자식클래스 타입 + 부모클래스로 생성은 불가능하다.
Parrot parrot2 = new Bird(); >> Error!
정리하면, 하위클래스의 인스턴스(객체)는 상위클래스의 인스턴스로도 사용될 수 있다. 하지만 반대로 성립될 수 없다.
왜냐하면 앵무새(Parrot class)는 그 상위인 새(Bird class)라고 말할 수 있지만, 모든 새가 앵무새는 아니기 때문이다! 따라서 상위(부모)클래스의 인스턴스는 하위(자식)클래스의 인스턴스로 사용될 수 없다.