상속과 관련돼 있는 다형성에 대해 다뤄보자.

간단한 트리 구조로
1. A에 있는 자료형을 B가 물려 받고, 그럼 D, E는 A, B의 자료형을 물려받을 수 있다.
2. A에 있는 자료형을 C가 물려 받고, 그럼 F는 A, C의 자료형을 물려받을 수 있다.
Animal
public class Animal {
public void eat() {
System.out.println("동물이 먹이를 먹습니다.");
}
public void run() {
System.out.println("동물이 달립니다.");
}
public void cry() {
System.out.println("동물이 울음소리를 냅니다.");
}
}
Rabbit
public class Rabbit extends Animal {
@Override
public void eat() {
System.out.println("토끼가 풀을 뜯어먹고 있습니다.");
}
@Override
public void run() {
System.out.println("토끼가 달립니다 껑충");
}
@Override
public void cry() {
System.out.println("토끼가 울음소리를 냅니다. 끼익~~");
}
public void jump() {
System.out.println("토끼가 점프를 뜁니다!");
}
}
Tiger
public class Tiger extends Animal {
@Override
public void eat() {
System.out.println("호랑이가 고기를 뜯어먹습니다.");
}
@Override
public void run() {
System.out.println("호랑이는 웬만에선 달리지 않습니다.");
}
@Override
public void cry() {
System.out.println("호랑이가 울부짖습니다.");
}
public void bite() {
System.out.println("호랑이가 물어뜯습니다.");
}
}
Tiger, Rabbit은 Animal을 상속해서 Animal의 자료형을 물려받을 수 있지만, Animal은 Tiger, Rabbit의 존재를 모른다.
Application
package com.ohgiraffers.section01.polymorphism;
public class Application {
public static void main(String[] args) {
/* 수업목표. 다형성과 타입 형변환에 대해 이해할 수 있다. */
Animal animal = new Animal();
animal.eat();
animal.run();
animal.cry();
System.out.println();
Tiger tiger = new Tiger();
tiger.eat();
tiger.run();
tiger.cry();
tiger.bite();
System.out.println();
Rabbit rabbit = new Rabbit();
rabbit.eat();
rabbit.run();
rabbit.cry();
rabbit.jump();
Animal an1 = new Animal(); // 다형성 X
Animal an2 = new Tiger(); // 다형성 O
Animal an3 = new Rabbit(); // 다형성 O
}
}
위 코드의 맨 밑 2줄을 보면 다형성 적용을 한 것을 알 수 있다.
아래 2가지는 다형성을 적용한 것이 아니다. 애초에 에러가 뜬다.
Tiger tiger = new Animal(); // 다형성 X
Rabbit rabbit = new Animal(); // 다형성 X
Animal은Tiger나Rabbit이 아니기 때문에 안 된다. (반대는 가능하니까 O)
Application 위 코드 아래에 추가해보자.
System.out.println("===== 동적 바인딩 확인하기 =====");
an1.cry();
an2.cry();
an3.cry();

cry()를 들어가보면 셋다 Animal의 cry 메소드를 쳐다보고 있다.
컴파일 시점 -> 정적 바인딩을 통해 Animal 타입의 cry() 메소드로 생각한다.
실행하면 (런타임 시점) 객체가 생성된다. 그래서 new Tiger(), new Rabbit() 들로 인해 어떤 타입들이 구체화 돼 있는지 알 수 있어서 사진과 같이 출력되는 것이다.

동적바인딩의 조건
1. 상속
2. 오버라이딩
동적 바인딩 : 컴파일 당시에는 해당 타입의 메소드와 연결돼 있다가
런타임 당시 실제 객체가 가진 오버라이딩 된 메소드로 바인딩이 바뀌어 동작하는 것을 의미한다.
부모타입을 자식타입으로 강제 형변환 하는 것은 가능하다 (조심해야 함)
Application
((Tiger) an3).cry();
// 컴파일 시점에서 에러 발생 X지만, 런타임 시점에 에러 발생
컴퓨터는 an3 를 Animal an3 = new Rabbit() 으로 적어놔도 컴파일 시점에서 an3는 현재 Animal 자료형인 것이다.
그래서 위 코드에서는 an3는 Animal 이기에 강제 형변환이 가능하지만, 런타임 시 an3는 Rabbit 타입인걸 알게되므로 런타임 에러가 생기는 것이다.
if (an3 instanceof Tiger) {
((Tiger) an3).cry();
}
if (an3 instanceof Animal) {
System.out.println("Animal은 맞지");
}
an3는 위에서 Rabbit으로 만들었었는데 둘다 가능할까?
아래 조건문의 실행부분만 가능하다.
instanceof: 해당 객체의 타입을 런타임 시점에 확인하기 위한 연산자
Animal an4 = new Tiger(); // 다형성을 적용, 자동형변환(auto up-casting), 묵시적 형변환
Rabbit rabbit2 = (Rabbit)an3; // 다형성 적용 X, 강제형변환(down-casting), 명시적 형변환
Tiger가 상위 타입 형변환으로 Animal로 클래스 형변환이 되는 것과
an3는 Animal인데 Rabbit으로 down-casting 된다.