Polymorphism (다형성)
다형성은 하나의 이름을 가진 클래스나 메서드가 여러 가지 형태의 동작을 하는 능력이다.
다형성에 의해 자손 타입 객체가 부모 타입으로 자동 형 변환이 될 수 있다.
즉, 부모 타입인 객체만으로도 상속된 모든 자손 타입들을 담을 수 있다.
클래스에서 다형성 예시로 첫 번째는 한 타입의 참조 변수로 여러 타입의 객체를 참조할 수 있는 것이다.
두 번째는 조상 클래스 타입의 참조 변수로 자손 클래스의 인스턴스를 참조할 수 있는 것이다.
메서드에서의 다형성은 메서드 오버라이딩과 메서드 오버로딩이 있다.
HAS-A (멤버), IS-A (상속)
HAS-A 관계 같은 경우는 멤버로 설계하고, IS-A 관계는 상속으로 설계한다.
HAS-A
- Car is-a Tire (X)
- Tire is-a Car (X)
- Car has-a Tire (O)
has-a 관계는 A를 가지고 있냐이다.
자동차를 예시로 들었을 때, 자동차가 가지는 것은 자동차의 부품이 있을 것이다. 자동차는 바퀴를 가지고 있기 때문에 자동차와 바퀴는 has-a 관계로 표현한다.
사람을 예시로 들면, 사람은 장기를 가지고 있을 것이다. Human has-a heart. 같은 의미이다.
IS-A
- Vehicle is-a Car (X)
- Car is-a Vehicle (O)
- HybridCar is-a Car (O)
is-a 관계는 A로 정의되는지를 나타낸다.
X is-a Y 일 때, Y의 범위가 X의 범위보다 크다. X는 Y 범위 내에 속한다.
사람을 예시로 들 때, 사람은 영장류이다-Human is-a Primates-. 라고 표현할 수 있다.
영장류는 사람이다. 는 거짓된 전제이므로, 자동차를 예시로 들었을 때 Vehicle is-a Car.는 올바르지 않은 is-a 관계인 것이다.
💻 예제 1
📝 소스 코드
public class Vehicle {
int speed;
public int getSpeed() {
return speed;
}
public void setSpeed(int speed) {
this.speed = speed;
}
public void displayInfo() {
System.out.println("--- Vehicle 정보 ---");
System.out.println("speed : " + speed);
}
}
public class Car extends Vehicle {
int oil;
public int getOil() {
return oil;
}
public void setOil(int oil) {
this.oil = oil;
}
@Override
public void displayInfo() {
System.out.println("--- Car 정보 ---");
System.out.println("speed : " + getSpeed());
System.out.println("oil : " + oil);
}
}
public class HybridCar extends Car {
int electricity;
public int getElectricity() {
return electricity;
}
public void setElectricity(int electricity) {
this.electricity = electricity;
}
@Override
public void displayInfo() {
System.out.println("--- HybridCar 정보 ---");
System.out.println("speed : " + getSpeed());
System.out.println("oil : " + getOil());
System.out.println("electricity : " + electricity);
}
}
Car 클래스는 Vehicle 클래스를 상속하고, HybridCar 클래스는 Car 클래스를 상속한다.
(조상) Vehicle - Car - HybridCar (자손)
public class Main {
public static void main(String[] args) {
A aaa = new A();
// B bbb = aaa;
Vehicle v1 = new Vehicle();
Car c1 = new Car();
HybridCar h1 = new HybridCar();
v1.displayInfo();
System.out.println();
c1.displayInfo();
System.out.println();
h1.displayInfo();
System.out.println();
System.out.println();
Vehicle car1 = new Car();
Vehicle car2 = new HybridCar();
Car car3 = new HybridCar();
car1.displayInfo();
System.out.println();
car2.displayInfo();
System.out.println();
car3.displayInfo();
// HybridCar car4 = new Vehicle();
}
}
class A {
}
class B {
}
주석 처리한 부분은 오류가 발생하는 부분이다.
B bbb = aaa;
Type mismatch: cannot convert from A to B라는 오류 문구가 뜬다.
A와 B는 다른 타입이다. 기본적으로 다른 타입의 객체는 참조할 수 없다.
(Vehicle) v1, (Car) c1, (HybirdCar) h1은 변수 타입에 맞는 객체를 생성해 주었다.
따라서 각각의 타입에 오버라이딩된 메서드가 동작한다.
car1, car2, car3는 좀 다르다.
각각의 변수 타입(Vehicle, Vehicle, Car)과 관계없이 오버라이딩된 메서드가 동작한다.
HybridCar car4 = new Vehicle();
자손(HybirdCar)에 조상(Vehicle)을 대입했다.
자손에 조상을 대입하는 건 불가능한 것으로 Type mismatch 오류가 발생한다.
📄 실행 결과
💻 예제 2
Vehicle, Car, HybridCar 클래스는 예제 1과 동일하다.
📝 소스 코드
public class Main {
public static void main(String[] args) {
// 자식 타입 객체가 부모 타입으로 참조 가능 (형변환 가능)
Vehicle car1 = new Vehicle();
Vehicle car2 = new Car();
Vehicle car3 = new HybridCar();
// 부모 타입으로 모든 자손 타입들을 담을 수 있다.
Vehicle[] car = new Vehicle[3];
car[0] = new Vehicle();
car[1] = new Car();
car[2] = new HybridCar();
for (int i = 0; i < car.length; i++) {
car[i].displayInfo();
}
System.out.println();
System.out.println(car1);
System.out.println(car2);
System.out.println(car3);
System.out.println();
Vehicle car4 = new Vehicle();
Car car5 = new Car();
HybridCar car6 = new HybridCar();
driveCar(car4);
driveCar(car5);
driveCar(car6);
}
public static void driveCar(Vehicle v) {
v.setSpeed(100);
v.displayInfo();
}
}
부모 타입으로 모든 자손 타입들을 담을 수 있기 때문에 Vehicle 타입에 Car와 HybridCar가 담길 것이다.
이는 곧 Car 타입으로 HybirdCar 타입을 담을 수도 있다는 뜻이다.
print 함수의 매개변수로 Object의 참조 변수가 넘어가면 내부적으로 해당 클래스의 toString() 메서드가 불려진다.
car[0], car[1], car[2] 모두 Vehicle 타입으로 담았지만 각각 생성한 타입이 출력되는 걸 볼 수 있다. 생성한 클래스의 toString() 메서드가 불려진 것이다.
만약 다형성이 없었다면 for문 코드를 아래와 같이 작성해야 했을 것이다.
Vehicle car1 = new Vehicle();
Car car2 = new Car();
HybridCar car3 = new HybridCar();
car1.displayInfo();
car2.displayInfo();
car3.displayInfo();
📄 실행 결과
💻 예제 3
📝 소스 코드
public class Main {
public static void main(String[] args) {
Vehicle car = new Car();
System.out.println(car.getSpeed());
car.setSpeed(10);
System.out.println(car.getSpeed());
System.out.println();
// car.setOil(100);
System.out.println(((Car) car).getOil());
((Car) car).setOil(100);
System.out.println(((Car) car).getOil());
}
}
Car 타입으로 생성했어도 이를 Vehicle 타입으로 담았기 때문에 Vehicle 클래스가 아닌 다른 클래스에 정의된 메서드는 사용할 수 없다.
실제로 Car 타입 인스턴스로 생성되어 print() 메서드에서는 생성한 타입의 toString() 메서드가 불려졌지만, 클래스 내에서 직접 생성한 메서드는 접근이 불가한 것이다.
생성한 클래스 내 메서드를 사용하기 위해서는 형변환(casting)을 통해 타입을 변환해야 한다.
getSpeed(), setSpeed(int) 메서드는 Vehicle 클래스 내에 있는 메서드라 사용 가능하다.
그러나 getOil(), setOil(int) 메서드는 Car 클래스에는 있지만 Vehicle 클래스에는 존재하지 않는 메서드라 사용 불가하다.
실제 오류 문구도 'The method setOil(int) is undefined for the type Vehicle'이라 나온다.
아래 부분에서는 Vehicle 타입에 담긴 car 앞에 (Car)를 사용하여 Car 타입으로 형 변환해 주어 Car 클래스 내에 있는 메서드들도 사용 가능한 것을 확인할 수 있다.
📄 실행 결과