💁♀️ 다형성(Polymorphism)이란,
하나의 인스턴스가 여러가지 타입을 가질 수 있는 것을 의미하며, 다형성은 상속을 기반으로 한 기술
public class Animal {
// 동물의 기본적인 행동인 먹는 행동과 뛰는 행동, 우는 행동의 메소드
public void eat() {
System.out.println("동물이 먹이를 먹습니다.");
}
public void run() {
System.out.println("동물이 달려갑니다.");
}
public void cry() {
System.out.println("동물이 울음소리를 냅니다.");
}
}
public class Rabbit extends Animal {
// 동물이 하는 행동을 보다 구체화하여 행동할 수 있도록 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("토끼가 점프합니다. 점-프!");
} >>> 이것은 Animal의 메소드를 Override한 것이 아닌 Rabbit 고유의 메소드
}
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("호랑이가 물어뜯습니다. 와앙!");
} >>> 이것은 Animal의 메소드를 Override한 것이 아닌 Tiger 고유의 메소드
}
📌 Ref.
* Rabbit은 Rabbit 타입이기도 하면서 Animal 타입, Tiger는 Tiger 타입이기도 하면서 Animal 타입.
=> 그렇기 때문에 '하나의 타입으로 여러 타입의 인스턴스를 처리'할 수 있기도 하고, 하나의 메소드 호출로
객체 별로 각각 다른 방법으로 동작하게 할 수도 있음
부모클래스인 Animal은 a1, a2를 통해 자식 클래스인 Rabbit, Tiger를 참조할 수 있음
Animal a1 = new Rabbit();
Animal a2 = new Tiger();
>>> 하지만 반대로 Animal은 Animal일 뿐 Rabbit, Tiger가 될 수 없음
>>> 따라서, 반대로 작성 시 에러가 발생
// Rabbit r = new Animal(); // Type mismatch
// Tiger t = (Tiger) new Animal();
>>> (Tiger)라는 형변환 연산자가 붙으면 컴파일 에러는 사라지지만 실행시키면,
>>> java.lang.ClassCastException 에러
>>> 컴파일 당시에는 해당 타입(Animal)의 메소드와 연결되어있음(정적 바인딩)
>>> 하지만 런타임 당시에는 실제 객체가 가지는 오버라이딩 된 메소드로 바인딩이 바뀌어 동작(동적 바인딩)
System.out.println("동적 바인딩 확인 -----------------");
a1.cry();
a2.cry();
>>> Animal 클래스의 메소드와 연결되어 있지만, Rabbit 클래스의 메소드가 출력됨
>>> 현재 레퍼런스 변수의 타입은 Animal이기 때문에 Rabbit과 Tiger가 가지고 있는 고유한 기능을 동작 X
// a1.jump();
// a2.bite();
>>> 객체 별로 고유한 기능을 동작시키기 위해 레퍼런스 변수를 형변환하여 Rabbit과 Tiger로 변경해야
>>> 메소드 호출 가능 (class type casting : 클래스 형변환)
System.out.println("클래스 타입 형변환 확인 -------------------");
// (Rabbit)a1.jump(); >>> 형변환보다 참조연산자 먼저 동작하여 오류
((Rabbit)a1).jump(); >>> 한번 더 소괄호로 감싸 형변환이 참조연산자보다 먼저 동작하게끔 만듬
((Tiger)a2).bite();
레퍼런스변수명 instanceof 클래스타입명
>>> 타입 형변환을 잘못 하는 경우 컴파일 시에는 문제가 없지만 런타임 시 Exception이 발생
>>> (java.lang.ClassCastException)
// ((Tiger)a1).bite();
>>> 컴파일 에러는 나지않지만 런타임 에러
>>> (Rabbit cannot be cast to class com.greedy.section01.polymorphism.Tiger)
System.out.println("intstanceof 확인 --------------------");
>>> : a1이 참조하고 있는 클래스가 Tiger가 맞나요? True or False
System.out.println("a1이 Tiger 타입인지 확인 : " + (a1 instanceof Tiger));
System.out.println("a1이 Rabbit 타입인지 확인 : " + (a1 instanceof Rabbit));
System.out.println("a2가 Tiger 타입인지 확인 : " + (a2 instanceof Tiger));
System.out.println("a2가 Rabbit 타입인지 확인 : " + (a2 instanceof Rabbit));
>>> 상속받은 타입도 함께 가지고 있음 (다형성)
System.out.println("a1이 Animal 타입인지 확인 : " + (a1 instanceof Animal)); // true
System.out.println("a2가 Animal 타입인지 확인 : " + (a2 instanceof Animal)); // true
>>> 모든 클래스는 Object의 후손
System.out.println("a1이 Object 타입인지 확인 : " + (a1 instanceof Object)); // true
System.out.println("a2가 Object 타입인지 확인 : " + (a2 instanceof Object)); // true
>>> 이런 식으로 이대로 클래스 형변환을 사용해도 되는지 확인용으로 instanceof를 사용
if(a1 instanceof Rabbit) {
((Rabbit)a1).jump(); >>> down-casting 함(원래는 Animal을 참조하고있으므로)
}
if(a2 instanceof Tiger) {
((Tiger)a2).bite(); >>> down-casting 함(원래는 Animal을 참조하고있으므로)
}
>>> 클래스 형변환은 up-casting과 down-casting으로 구분할 수 있음
>>> up-casting : 상위 타입으로 형변환
>>> down-casting : 하위 타입으로 형변환
>>> up-casting의 경우 자동 형변환(묵시적 형변환)이 적용
Animal animal1 = new Rabbit(); >>> 이 식은 아래와 같이 묵시적 형변환이 일어나고 있음
Animal animal2 = (Animal) new Rabbit();
>>> down-casting의 경우 강제 형변환(명시적 형변환)을 해야함
// Rabbit rabbit1 = animal1; >>> 꼭 앞에 아래와 같이 명시적 형변환을 해줘야함 (Type mismatch 에러)
Rabbit rabbit2 = (Rabbit) animal2;
// 상위 타입의 레퍼런스 배열을 만들고 각 인덱스에 인스턴스를 생성해서 대입
Animal[] animals = new Animal[5];
animals[0] = new Rabbit();
animals[1] = new Tiger();
animals[2] = new Rabbit();
animals[3] = new Tiger();
animals[4] = new Rabbit();
>>> Animal 클래스가 가진 메소드를 오버라이딩한 메소드 호출 시 동적 바인딩을 이용할 수 있음
for(int i = 0; i < animals.length; i++) {
animals[i].eat();
animals[i].run();
animals[i].cry();
}
System.out.println();
>>> 각 클래스별 고유 메소드를 동작시키기 위해서는 down-casting을 명시적으로 해주어야하는데
>>> ClassCastException을 방지하기 위해서 instanceof 연산자를 이용해야 함
for(int i = 0; i < animals.length; i++) {
if(animals[i] instanceof Rabbit) {
((Rabbit)animals[i]).jump();
} else if(animals[i] instanceof Tiger) {
((Tiger)animals[i]).bite();
} else {
System.out.println("호랑이나 토끼가 아닙니다.");
}
}
public class Application3 {
public static void main(String[] args) {
// 아래의 feed 메소드를 사용하기 위한 객체 생성
Application3 app3 = new Application3();
// Animal 타입의 토끼와 호랑이 인스턴스를 만들어서 먹이를 줌
Animal animal1 = new Rabbit();
Animal animal2 = new Tiger();
// Rabbit 타입의 토끼와 Tiger 타입의 호랑이 인스턴스도 만들어봄
Rabbit rabbit = new Rabbit();
Tiger tiger = new Tiger();
app3.feed(animal1); >>> 동일한 타입이기 때문에 전달 가능
app3.feed(animal2);
app3.feed(rabbit); >>> 다형성이 적용되어 up-casting으로 묵시적 형변환이 일어나면서
feed(Animal animal)메소드에 적용시킬 수 있음
(Rabbit은 animal이니까)
app3.feed(tiger); >>> tiger라는 주소값을 feed메소드에 전달
// 동물에게 먹이를 주기 위한 용도의 메소드
>>> 이렇게 여러 동물들을 한꺼번에 다루기 위해 Animal이라는 상위 타입 객체를 일부러 만든 것
public void feed(Animal animal) {
animal.eat();
}
}
📌 Ref.
* 다형성을 적용하지 않았다면 호랑이, 토끼에게 먹이주는 메소드를 각각 별도로 작성해야 했을 것.
또한, 추가적을 다른 동물을 다뤄야 하는 경우도 그 메소드를 더 추가해야 했을 것. 하지만 다형성을
적용하면 하나의 메소드로 모든 하위 객체를 다룰 수 있어 별도의 메소드 작성 및 추가가 불필요
public class Application4 {
public static void main(String[] args) {
Application4 app4 = new Application4();
>>> 반환타입 변수 = app4.getRandomAnimal();
Animal randomAnimal = app4.getRandomAnimal();
randomAnimal.cry();
}
// 랜덤하게 토끼 또는 호랑이를 반환하는 메소드
public Animal getRandomAnimal() {
int random = (int)(Math.random() * 2);
return random == 0? new Rabbit() : new Tiger();
}
}