김영한의 실전 자바 기본편 - 8일차
다형성1
객체지향 프로그래밍의 대표적인 특징으로는 캡슐화, 상속, 다형성이 있다.
다형성은 이름 그대로 다양한 형태, 여러형태를 뜻한다.
한 객체가 여러 타입의 객체로 취급될 수 있는 능력을 뜻한다.
다형적 참조
public class Parent {
public void parentMethod(){
System.out.println("Parent.parentMethod");
}
}
public class Child extends Parent{
public void childMethod(){
System.out.println("Child.childMethod");
}
}
public class PolyMain {
public static void main(String[] args) {
System.out.println("Parent -> Parent");
Parent parent = new Parent();
parent.parentMethod();
System.out.println("Child -> Child");
Child child = new Child();
child.parentMethod();
child.childMethod();
System.out.println("Parent -> Child");
Parent poly = new Child();
poly.parentMethod();
}
}
실행결과
부모는 자식을 담을 수 있다.
- 부모 타입은 자식을 담을 수 있다.
- Parent poly는 부모 타입이다. new Child() 를 통해 생성된 결과는 Child() 타입이다. 자바에서 부모 타입은 자식 타입을 담을 수 있다.
- 반대로 자식타입은 부모 타입을 담을 수 없다.
Parent 타입의 변수는 자신인 Parent 는 물론이고 자식 타입까지 참조 할 수 있다 .만약 손자가 있다면 그 손자도 그 하위 타입도 참조할 수 있다.
이것을 바로 다형적 참조라 한다.
다형적 참조와 인스턴스 실행
Parent poly = new Child() 이렇게 자식을 참조한 상황에서 poly가 자식 타입인 child에 있는 childMethod()를 호출하면 컴파일 오류가 발생한다. poly는 Parent 타입이기 때문에 Parent 클래스부터 시작해서 필요한 기능을 찾는다. 상속 관계는 부모 방향으로 올라 갈 수있지만 자식 방향으로 찾아 내려갈수는 없다.
childMethod()를 호출하고 싶다면 캐스팅이 필요하다.
다형성과 캐스팅
public class CastingMain1 {
public static void main(String[] args) {
// 부모 변수가 자식 인스턴스 참조(다형적 참조)
Parent poly = new Child();
//단 자식의 기능은 호출할 수 없다. 컴파일 오류 발생
//poly.childMethod();
//다운캐스팅(부모 타입 -> 자식 타입)
Child child = (Child) poly;
child.childMethod();
}
}
다운 캐스팅
부모 타입을 사용하는 변수를 자식 타입에 대입하려고 하면 컴파일 오류가 발생한다. 자식은 부모를 담을 수 없다.
이때는 다운캐스팅이라는 기능을 사용해서 부모 타입을 잠깐 자식 타입으로 변경하면 된다.
업캐스팅: 부모 타입으로 변경
다운캐스팅: 자식 타입으로 변경
캐스팅의 종류
일시적 다운 캐스팅
public class CastingMain2 {
public static void main(String[] args) {
// 부모 변수가 자식 인스턴스 참조(다형적 참조)
Parent poly = new Child();
//단 자식의 기능은 호출할 수 없다. 컴파일 오류 발생
//poly.childMethod();
//다운캐스팅(부모 타입 -> 자식 타입)
// Child child = (Child) poly;
// child.childMethod();
//일시적 다운캐스팅 - 해당 메서드를 호출하는 순간만 다운캐스팅
((Child) poly).childMethod();
}
}
일시적 다운캐스팅을 사용하면 별도의 변수 없이 인스턴스의 자식 타입의 기능을 사용할 수 있다.
업캐스팅
public class CastingMain3 {
public static void main(String[] args) {
Child child = new Child();
Parent parent1 = (Parent) child ;
Parent parent2 = child;
parent1.parentMethod();
parent2.parentMethod();
}
}
업캐스팅은 생략할 수 있다. 다운캐스팅은 생략할 수 없다.
다운캐스팅 주의점
//다운캐스팅을 자동으로 하지 않는 이유
public class CastingMain4 {
public static void main(String[] args) {
Parent parent1 = new Child();
Child child = (Child) parent1;
child.childMethod(); //문제 없음
Parent parent2 = new Parent();
Child child2 = (Child) parent2; //런타임 오류 - ClassCastException
child2.childMethod(); //실행 불가
}
}
parent2는 Parent로 생성이 되었다. 따라서 메모리 상에 Child 자체가 존재하지 않는다. Child 자체를 사용할 수 없다.
이렇게 사용할 수 없는 타입으로 다운캐스팅하는 경우에 ClassCastException이라는 예외를 발생 시킨다.
instanceof
다형성에서 참조형 변수는 이름 그대로 다양한 자식을 대상으로 참조할 수 있다.
참조하는 대상이 다양하기 때문에 어떤 인스턴스를 참조하고 있는지 확인하려면 instanceof를 사용하면 된다.
public class CastingMain5 {
public static void main(String[] args) {
Parent parent1 = new Parent();
System.out.println("parent1 호출");
call(parent1);
Parent parent2 = new Child();
System.out.println("parent2 호출");
call(parent2);
}
private static void call(Parent parent) {
parent.parentMethod();
if (parent instanceof Child) {
System.out.println("Child 인스턴스 맞음");
Child child = (Child)parent;
child.childMethod();
}else {
System.out.println("Child 인스턴스 아님");
}
}
}
다운캐스팅을 수행하기 전에는 먼저 instanceof를 사용해서 원하는 타입으로 변경이 가능한지 확인한 다음에 다운캐스팅을 수행하는 것이 안전하다.
parent1는 Parent의 인스턴스를 참조하므로 false를 반환한다.
parent2는 Child의 인스턴스를 참조하므로 true를 반환한다.
쉽게 이해하는 방법으로는
parent instanceof Parent //parent는 Child의 인스턴스
오른쪽의 Parent를 왼쪽에 대입하고
왼쪽의 parent를 오른쪽에 대입해서
Parent p = new Parent(); // 가 성립하면 true
parent instanceof Child
Child c = new Parent(); // false
다형성과 메서드 오버라이딩
오버라이딩 된 메서드가 항상 우선권을 가진다.
public class OverridingMain {
public static void main(String[] args) {
Child child = new Child();
System.out.println("Child -> Child");
System.out.println("value = " + child.value);
child.method();
Parent parent = new Parent();
System.out.println("Parent -> Parent");
System.out.println("value = " + parent.value);
parent.method();
Parent parent1 = new Child();
System.out.println("Parent -> Child ");
System.out.println("value = " + parent1.value);
parent1.method();
}
}
오버라이딩된 메서드는 항상 우선권을 가진다. 오버라이딩은 부모 타입에서 정의한 기능을 자식 타입에서 재정의하는 것이다.
만약 자식에서도 오버라이딩 하고 손자에서도 같은 메서드를 오버라이딩을 하면 손자의 오버라이딩 메서드가 우선권을 가진다.
더 하위 자식의 오버라이딩 된 메서드가 우선권을 가진다.
Reference