현실에서 다형성은 하나의 객체가 여러 가지 형태를 가질 수 있는 성질을 의미한다. 프로그래밍에서 또한 비슷한 의미를 지닌다.
자바 프로그래밍에서 다형성은 한 타입의 참조변수를 통해 여러 타입의 객체를 참조할 수 있도록 만든 것을 의미한다.
즉, 상위 클래스 타입의 참조변수를 통해서 하위 클래스의 객체를 참조할 수 있도록 허용한 것이라 할 수 있다.
class Friend {
public void friendInfo() {
System.out.println("나는 당신의 친구입니다.");
}
}
class BoyFriend extends Friend {
public void friendInfo() {
System.out.println("나는 당신의 남자친구입니다.");
}
}
class GirlFriend extends Friend {
public void friendInfo() {
System.out.println("나는 당신의 여자친구입니다.");
}
}
public class FriendTest {
public static void main(String[] args) {
Friend friend = new Friend(); // 객체 타입과 참조변수 타입의 일치
BoyFriend boyfriend = new BoyFriend();
Friend girlfriend = new GirlFriend(); // 객체 타입과 참조변수 타입의 불일치
friend.friendInfo();
boyfriend.friendInfo();
girlfriend.friendInfo();
}
}
// 출력값
나는 당신의 친구입니다.
나는 당신의 남자친구입니다.
나는 당신의 여자친구입니다.
위의 예시에서 상위 클래스 Friend를 참조변수의 타입으로 지정하고 있다. 이 경우, 참조변수가 사용할 수 있는 멤버의 개수는 상위 클래스의 멤버의 수가 된다.
public class FriendTest {
public static void main(String[] args) {
Friend friend = new Friend(); // 객체 타입과 참조변수 타입의 일치 -> 가능
BoyFriend boyfriend = new BoyFriend();
Friend girlfriend = new GirlFriend(); // 객체 타입과 참조변수 타입의 불일치 -> 가능
// GirlFriend friend1 = new Friend(); -> 하위클래스 타입으로 상위클래스 객체 참조 -> 불가능
friend.friendInfo();
boyfriend.friendInfo();
girlfriend.friendInfo();
}
}
다형성은 상위 클래스의 타입으로 하위 클래스 타입의 객체를 참조하는 것은 가능하지만, 그 반대는 성립하지 않는다. 그 이유는 실제 객체인 Friend의 멤버 개수보다 참조변수 friend1이 사용할 수 있는 멤버 개수가 더 많기 때문이다.
좀 더 구체적으로 설명하면, 실제 참조하고 있는 인스턴스의 멤버를 기준으로 참조 변수의 타입의 멤버가 실제 인스턴스의 멤버 수보다 작은 것은 실제 사용할 수 있는 기능을 줄이는 것이기에 허용되지만, 그 반대의 경우는 참조하고 있는 인스턴스에 실제로 구현된 기능이 없어 사용이 불가하기 때문이다.
기본 자료형의 형변환처럼, 참조 변수도 타입변환이 가능하다.
참조 변수의 타입 변환은 사용할 수 있는 멤버의 개수를 조절하는 것을 의미하는데 상속관계에 있는 클래스들 사이에서만 타입 변환이 가능하다.
public class VehicleTest {
public static void main(String[] args) {
Car car = new Car();
Vehicle vehicle = (Vehicle) car; // 상위 클래스 Vehicle 타입으로 변환(생략 가능)
Car car2 = (Car) vehicle; // 하위 클래스 Car타입으로 변환(생략 불가능)
MotorBike motorBike = (MotorBike) car; // 상속관계가 아니므로 타입 변환 불가 -> 에러발생
}
}
class Vehicle {
String model;
String color;
int wheels;
void startEngine() {
System.out.println("시동 걸기");
}
void accelerate() {
System.out.println("속도 올리기");
}
void brake() {
System.out.println("브레이크!");
}
}
class Car extends Vehicle {
void giveRide() {
System.out.println("다른 사람 태우기");
}
}
class MotorBike extends Vehicle {
void performance() {
System.out.println("묘기 부리기");
}
}
Car 클래스의 인스턴스 객체 car를 생성하고, 그 객체를 가리키는 참조변수 vehicle의 타입을 Vehicle로 지정하여 참조변수의 타입변환을 실행한다. 그 후 반대로 vehicle를 하위 클래스 타입인 Car로 타입 변환하여 참조변수 car2에 할당했다.
이처럼 상속관계에 있는 클래스 간에는 상호 타입변환이 자유롭게 수행될 수 있다.
하지만, Car 클래스와 MotorBike 클래스는 상속관계가 아니므로 타입 변환이 불가하여 에러가 발생하는 것을 확인할 수 있다.
참조_변수 instanceof 타입을 입력했을 때 리턴 값이 true가 나오면 참조 변수가 검사한 타입으로 타입 변환이 가능하며, 반대로 false가 나오는 경우에는 타입 변환이 불가능하다.
public class InstanceOfExample {
public static void main(String[] args) {
Animal animal = new Animal();
System.out.println(animal instanceof Object); //true
System.out.println(animal instanceof Animal); //true
System.out.println(animal instanceof Bat); //false
Animal cat = new Cat();
System.out.println(cat instanceof Object); //true
System.out.println(cat instanceof Animal); //true
System.out.println(cat instanceof Cat); //true
System.out.println(cat instanceof Bat); //false
}
}
class Animal {};
class Bat extends Animal{};
class Cat extends Animal{};
Cat 객체를 예로 들어보면, 생성된 객체는 Animal 타입으로 선언되어있지만 다형적 표현 방법에 따라 Object와 Animal 타입으로도 선언될 수 있다는 점을 확인하실 수 있다.
public class PolymorphismEx {
public static void main(String[] args) {
Customer customer = new Customer();
customer.buyCoffee(new Americano());
customer.buyCoffee(new CaffeLatte());
System.out.println("현재 잔액은 " + customer.money + "원 입니다.");
}
}
class Coffee {
int price;
public Coffee(int price) {
this.price = price;
}
}
class Americano extends Coffee {
public Americano() {
super(4000); // 상위 클래스 Coffee의 생성자를 호출
}
public String toString() {return "아메리카노";}; //Object클래스 toString()메서드 오버라이딩
};
class CaffeLatte extends Coffee {
public CaffeLatte() {
super(5000);
}
public String toString() {return "카페라떼";};
};
class Customer {
int money = 50000;
void buyCoffee(Coffee coffee) {
if (money < coffee.price) { // 물건 가격보다 돈이 없는 경우
System.out.println("잔액이 부족합니다.");
return;
}
money = money - coffee.price; // 가진 돈 - 커피 가격
System.out.println(coffee + "를 구입했습니다.");
}
}
// 출력값
아메리카노를 구입했습니다.
카페라떼를 구입했습니다.
현재 잔액은 41000원 입니다.
매개변수로 각각의 개별적인 커피의 타입이 아니라 상위클래스인 Coffee의 타입을 매개변수로 전달받으면, 그 하위클래스 타입의 참조변수면 어느 것이나 매개변수로 전달될 수 있고 이에 따라 매번 다른 타입의 참조변수를 매개변수로 전달해주어야하는 번거로움을 훨씬 줄일 수 있다.