구체적으로 말하면, 조상클래스 타입의 참조변수로 자식클래스의 인스턴스를 참조하는 것을 다형성이라 한다.
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.print(text); }
}
Tv 클래스와 CaptionTv의 관계를 나타내면 이렇게 된다.
Tv 와 CaptionTv 는 상속 관계에 있다.
두 클래스의 인스턴스를 생성하기 위해서는 지금까지는 이렇게 했었다.
Tv t = new Tv();
CaptionTv c = new CaptionTv();
Tv 인스턴스를 생성하기 위해서는 Tv 타입의 참조변수를,
CaptionTv 인스턴스를 생성하기 위해서는 CaptionTv 타입의 참조변수를 사용했다.
다형성을 이용하면 상속관계에 있는 클래스들의 인스턴스를 생성할 때,
조상 클래스 타입의 참조변수로 자식 클래스의 인스턴스를 참조할 수 있다.
Tv t = new CaptionTv();
CaptionTv c = new CaptionTv();

두 변수의 인스턴스가 모두 CaptionTv 이라 할지라도, t는 CaptionTv의 모든 멤버를 사용할 수 없다.
t는 CaptionTv 인스턴스 중 Tv 클래스의 멤버들(상속받은 멤버 포함)만 사용할 수 있다. Tv 클래스에 정의되지 않은, text 나 caption( )은 사용이 불가능하다.
둘 다 같은 타입의 인스턴스지만 참조변수의 타입에 따라
사용할 수 있는 멤버의 개수가 달라진다.
반대로 자식 타입의 참조변수로 부모 타입의 인스턴스를 참조하는 것은 가능할까?
CaptionTv c = new Tv();
이유는 인스턴스인 Tv의 멤버보다 참조변수의 타입인 CaptionTv의 멤버가 더 많기 때문이다.
CaptionTv 클래스에는 text 와 caption( )이 선언되어 있기 때문에 참조변수 c가 인스턴스에서 사용하려 시도할 수 있다.
그러나 실제 Tv 클래스에는 text 와 caption( )이 존재하지 않기 때문에 문제가 발생한다.
이러한 문제가 있을 수 있기에 자식타입의 참조변수로 부모 타입의 인스턴스를 참조하는 것은 허용하지 않는다.
- 부모 타입의 참조변수로 자식 타입 인스턴스 참조 가능
- 자식 타입의 참조변수로 부모 타입 인스턴스 참조 불가능
- 참조변수가 사용할 수 있는 멤버의 개수는
인스턴스의 멤버 개수보다 같거나 적어야 함
기본형 변수와 마찬가지로 참조형 변수도 형변환이 가능하다.
단, 서로 상속관계에 있는 클래스 사이에서만 가능하다.
업 캐스팅 : 자식 타입 ㅡ> 부모 타입 ( 형변환 생략 가능 )
다운 캐스팅 : 부모 타입 ㅡ> 자식 타입 ( 형변환 생략 불가 )
참조변수 간의 형변환 역시 캐스트연산자를 사용하며, 괄호( )안에 변환하고자 하는 타입의 클래스명을 적으면 된다.
class Animal { ... }
class Dog extends Animal { ... }
class Cat extends Animal { ... }
자바에서는 조상과 자식의 관계만 존재한다.
자식 간의 관계는 아무 관계도 아니다. 즉, Dog 클래스와 Cat 클래스는 아무런 관계가 없다.
class Main {
public static void main(String[] args) {
Parent p = new Parent();
Child c = new Child();
Parent pc = new Child(); // 업캐스팅
p.print();
c.print();
pc.print();
}
}
class Parent {
void print() {
System.out.println("I'm Parent");
}
}
class Child extends Parent{
void print() {
System.out.println("I'm Child");
}
}
실행을 하게 되면 업캐스팅된 pc의 메소드는
오버라이딩이 된 I'm Child 가 출력된다.
Parent pc = new Child();
이 문장은 한 번에 대입하면서 캐스팅한 것으로,
변수에 나눠 대입할 수도 있다.
Child cc = new Child();
Parent pc = cc;
업캐스팅 시 멤버가 제한되어 자식 클래스의 멤버는 사용할 수 없게 된다.
오버라이딩된 메소드는 자식 클래스의 메소드로 실행이 된다.
주의할 점은 다운 캐스팅은 단순히 업 캐스팅의 반대 개념이 아니다.
다운 캐스팅의 의미는 업 캐스팅으로 부모 클래스가 된 객체를 다시 자식 클래스로 복구하여 본인의 기능을 회복하여 사용할 수 있게 하기 위한 것이다!
Parent p = new Parent();
Child cp = (Child)p;
이렇게 생 부모 객체를 자식 클래스 타입으로 캐스팅하면
오류(ClassCastException)가 발생한다.
위에서 말했던 것처럼 자식 클래스 타입의 참조변수에는 부모 클래스의 인스턴스를 담을 수 없는, 참조 다형성 위배가 되기 때문이다.
다운 캐스팅에 대해 주의해야하는 이유는, 컴파일 에러는 나지 않지만 런타임 에러가 발생하기 때문이다.
Child c = new Child();
Parent pc = (Parent)c; // Parent 클래스 타입으로 업 캐스팅
c = (Child)pc;
부모 클래스 타입으로 사용하다가 자식 클래스에만 있는 멤버를 사용하기 위해 다운 캐스팅을 통해 기능을 사용할 수 있다.
위에서 Animal 클래스의 예시를 들면서
형제 관계인 Dog 와 Cat 은 서로 형변환이 불가능하다고 말했다.
Dog d = new Dog();
Animal an = d; // 업 캐스팅
Cat c = (Cat)an; // Cat 클래스 타입으로 다운 캐스팅
// ClassCastException 발생
위의 코드와 같이 무분별하게 다운 캐스팅을 하다보면 런타임 에러가 발생할 수 있다.
다행히 자바에서는 혼동되는 객체를 구별하기 위한 instanceof 연산자를 제공한다.
참조변수가 참조하고 있는 인스턴스의 실제 타입을 알아보기 위해 사용되는 연산자이다.
instanceof 의 왼쪽에는 참조변수를, 오른쪽에는 타입(클래스명)이 위치한다.
연산 결과는 true/false 중 하나를 반환한다.
true를 얻었다는 것은 참조변수가 검사한 타입(클래스명)으로 캐스팅이 가능하다는 것을 의미한다.
void doCasting(Animal animal) {
if (animal instanceof Dog) {
Dog d = (Dog)animal;
} else if (animal instanceof Cat) {
Cat c = (Cat)animal;
}
}
위의 메소드는 Animal 클래스 타입의 참조변수를 매개변수로 한다.
매개변수 animal이 Animal 클래스의 인스턴스인지 그 자식 클래스의 인스턴스인지 알 수가 없다.
그럴 때 instanceof 연산자를 이용하여 참조 변수 animal이 가리키는 인스턴스 타입을 체크하고 다운 캐스팅을 진행한다.
instanceof의 결과가 true를 반환한다는 것은 캐스팅이 가능하다는 의미라고 했다.
부모 클래스로의 캐스팅은 항상 가능하기 때문에 instanceof의 오른쪽 피연산자가 상위 클래스라면 항상 true 값을 얻는다.
class Parent {
int x = 100;
void method() {
System.out.println("Parent Method");
}
}
class Child extends Parent {
int x = 200;
void method() {
System.out.println("Child Method");
}
}
위의 코드처럼 부모 클래스와 자식 클래스의 멤버가 중복 정의된 경우,
public class Main {
public static void main(String[] args) {
Parent p = new Parent();
Parent pc = new Child();
Child c = new Child();
System.out.println("p.x : " + p.x);
System.out.println("pc.x : " + pc.x);
System.out.println("c.x : " + c.x);
p.method();
pc.method();
c.method();
}
}
실행 결과 :
p.x : 100
pc.x : 100
c.x : 200
Parent Method
Child Method
Child Method