다형성 (Polymorphism)
객체지향개념에서 다형성이란 하나의 객체가 여러 형태를 가지는 것이다. '여러 개'를 의미하는 poly와 '형태'를 의미하는 morphism을 합쳐 polymorphism, 다형성이라고 부른다.
자바에서 다형성이란, 한 타입의 참조변수로 여러 타입의 객체를 참조할 수 있도록 만드는 것이다. 좀 더 구체적으로 말하자면, 조상/상위 클래스 타입의 참조변수로 하위/자손 클래스의 인스턴스를 참조할 수 있도록 하는 것이다.
class Coffee {
int price;
}
class Americano extends Coffee {
String name;
void recipe()
}
위와 같이 Coffee 상위 클래스와 Americano 하위 클래스가 상속 관계에 있다고 했을 때,
Coffee coffee = new Coffee();
Americano americano = new Americano();
각 클래스에서 coffee와 americano라는 객체를 생성하는 것이 일반적이다. 그러나 다형성은
Coffee americano = new Americano();
상위 클래스 타입의 참조변수로 Americano 인스턴스를 참조한다.
인스턴스를 같은 타입의 참조변수로 참조한다면 coffee라는 참조변수를 통해 접근할 수 있는 멤버의 개수가 int price, String name, void recipe()로 총 3개다. 그러나 상위 클래스 타입의 참조변수로 참조한다면 사용할 수 있는 멤버가 int price 하나다. 참조변수를 리모컨으로 생각하면, 하위 클래스 타입의 리모컨일 때는 상위 클래스의 멤버를 상속 받아 멤버 개수가 동일하거나 많기 때문에 사용할 수 있는 기능이 많다. 그러나 상위 클래스 타입의 리모컨일 때는 하위 클래스의 추가된 멤버를 사용할 수 없기 때문에 멤버 개수가 같거나 더 적다.
Americano coffee = new Coffee(); // 컴파일 에러
반대로 하위 클래스 타입으로 상위 클래스의 인스턴스를 참조하는 것은 불가능하다. 하위 클래스 타입에만 있는 멤버를 상위 클래스에서 참조하게 될텐데 이는 존재하지 않는 기능을 찾는 것과 마찬가지이기 때문에 컴파일 에러를 일으킨다. 다형성을 쓰는 이유는 코드의 중복을 줄여 편하고 효율적인 프로그래밍을 할 수 있기 때문이다. 매번 다른 메서드를 만들 필요없이 같은 메서드를 덮어쓰거나 다양하게 활용하면 된다.
참조 변수의 타입 변환
자료형을 변환할 수 있는 것처럼, 참조 변수도 형변환이 가능하다. 단, 상속관계에 있는 클래스 간에서만 가능하기 때문에 하위 타입의 참조 변수를 상위 타입으로, 상위 타입의 참조변수를 하위 타입의 참조변수로 형변환하는 것만 가능하다. 하위👉상위일 때는 업캐스팅이라고 하는데 형변환 연산자인 괄호를 생략할 수 있다. 상위👉하위로 변환하는 다운캐스팅일 때는 반드시 괄호를 붙여줘야 한다. 같은 하위 클래스 간(형제 간) 형변환은 상속 관계가 아니기 때문에 불가능하다.
class Fruit { ... }
class Apple extends Fruit { ... }
Fruit라는 상위 클래스와 Fruit를 상속 받은 Apple라는 하위 클래스가 있을 때,
Fruit fruit = null;
Apple ap = new Apple();
Apple ap2 = new Apple();
fruit = ap; // fruit = (Fruit)ap; 에서 형변환 생략한 것
ap2 = (Apple)fruit;
위와 같이 상속 관계 간 참조변수의 형변환을 실행할 수 있다. 형변환 연산자인 괄호에 참조할 타입을 넣음으로서 대립연산자가 수행되기 전에 두 변수 간의 타입을 맞춰주는 것이다. 다운캐스팅시 형변환을 생략하면 안 되는 이유는 결국 상위👉하위인 경우 갖고 있는 멤버의 개수가 많아지는 것이라 문제가 발생할 가능성이 있기 때문이다. 따라서 형변환을 수행하기 전에 instanceof 연산자를 통해 인스턴스 타입을 확인하는 것이 안전하다.
Coffee americano = new Americano();
다형성에서 사용한 예제도 아래의 코드를 한 줄로 줄인 것이다.
Americano americano = new Americano();
Coffee am = (Coffee) americano;
💁♀️ instanceof 연산자
instanceof 연산자는 캐스팅 가능여부를 boolean 타입으로 확인할 수 있는 자바의 문법요소이다. 참조변수 instanceof 타입
을 입력했을 때 true가 나오면 형변환이 가능하며, false가 나온 경우 형변환이 불가능하다. 만약 참조 변수가 null인 경우에도 false를 반환한다.
다형성 활용 코드
문구점에서 펜, 종이, 지우개를 구입하는 상황을 다형성 개념을 활용하여 코드로 짜보았다. 펜, 종이, 지우개마다 구입하는 메서드를 개별로 작성하는 대신, customer.buySupplies(new Pen());
와 같은 코드로 펜, 종이, 지우개 객체를 customer2.buySupplies()
메소드에 할당해 코드의 중복을 줄였다.
public class PolymorphismExercise2 {
public static void main(String[] args) {
Customer2 customer2 = new Customer2();
customer2.buySupplies(new Pen());
customer2.buySupplies(new Paper());
customer2.buySupplies(new Eraser());
System.out.println("현재 잔액은 " + customer2.money + "원 입니다.");
}
}
class Customer2 {
int money = 10000;
void buySupplies (Supplies supplies) {
if(supplies.price > money) {
System.out.println("잔액이 부족합니다.");
}
money = money - supplies.price;
System.out.println(supplies + "를 구입했습니다.");
}
}
class Supplies {
int price;
public Supplies(int price) {
this.price = price;
}
}
class Pen extends Supplies {
public Pen() {
super(1000);
}
public String toString()
{return "펜";};
}
class Paper extends Supplies {
public Paper() {
super(3000);
}
public String toString()
{return "종이";};
}
class Eraser extends Supplies {
public Eraser() {
super(500);
}
public String toString()
{return "지우개";};
}
// 출력:
// 펜를 구입했습니다.
// 종이를 구입했습니다.
// 지우개를 구입했습니다.
// 현재 잔액은 5500원 입니다.