다형성(polymorphism)
다형성
- 조상타입 참조변수로 자손타입 객체를 다루는 것 ⭐️
- 객체와 참조변수의 타입이 일치할 때와 일치하지 않을 때의 차이
- 자손 타입의 참조변수로 조상 타입의 객체를 가리킬 수 없다.
조상타입 참조변수로 자손타입 객체를 다루는 것
- 여러 가지 형태를 가질 수 있는 능력
Tv t = new Tv();
SmartTv c = new SmartTv();
지금까지 우리는 생성된 인스턴스를 다루기 위해서, 인스턴스의 타입과 일치하는 타입의 참조변수만을 사용했다. 즉, Tv 인스턴스를 다루기 위해서는 Tv 타입의 참조변수를 사용하고, SmartTv인스턴스를 다루기 위해서는 SmartTv타입의 참조변수를 사용했다.
Tv t = new SmartTv(); // 타입 불일치 -> OK.
// 일반적으로 참조변수 타입과 객체 타입이 일치해야 함
// 하지만, 다형성은 타입이 불일치할 수 있고, 두 가지 '장점' 있음
타입이 불일치 할 경우 장점이 두 가지 있다.
객체와 참조변수의 타입이 일치할 때와 일치하지 않을 때의 차이?
SmartTv s = new SmartTv(); // 참조변수와 인스턴스의 타입이 일치
Tv t = new SmartTv(); // 조상 타입의 참조변수로 자손 타입의 인스턴스 참조


자손타입의 참조변수로 조상타입의 객체를 참조할 수 없다.
- 조상타입의 참조변수로 자손타입의 인스턴스를 참조하는 경우,
참조변수는 조상타입의 참조변수에 정의된 멤버만 사용할 수 있다. - 참조변수가 사용할 수 있는 멤버의 개수는 객체의 멤버 개수보다 적거나 같아야 한다.
- 자손타입의 참조변수로 조상타입의 객체를 참조할 수 없다.
Tv t = new SmartTv(); // Ok. 허용
SmartTv c = new Tv(); // 에러. 허용 안 됨


리모컨으로 인스턴스에 없는 기능을 사용하면 에러가 발생한다.
자주 묻는 질문 ❓
- Q1. 참조변수의 타입은 인스턴스의 타입과 반드시 일치해야 하나요?
- Q2. 참조변수가 조상타입일 때와 자손타입일 때의 차이는?
- Q3. 자손 타입의 참조변수로 조상 타입의 객체를 가리킬 수 있나요?
Q1. 참조변수의 타입은 인스턴스의 타입과 반드시 일치해야 하나요?
A. 아닙니다. 일치하는 것이 보통이지만 일치하지 않을 수도 있습니다.
Q2. 참조변수가 조상타입일 때와 자손타입일 때의 차이는?
A. 참조변수로 사용할 수 있는 멤버의 개수가 달라집니다.
Q3. 자손 타입의 참조변수로 조상 타입의 객체를 가리킬 수 있나요?
A. 아니요. 허용되지 않습니다.
참조변수의 형변환
- 사용할 수 있는 멤버의 갯수를 조절하는 것(리모컨 변경)
- 조상﹒자손관계의 참조변수만 서로 형변환 가능 ⭐
- 참조변수가 가리키는 실제 객체가 무엇인지가 중요하고, 그 멤버의 갯수를 넘어서면 안된다.
- 조상타입(4) ← 자손타입(5), 형변환 생략 불가(증가, 위험)
- 자손타입(5) ← 조상타입(4), 형변환 생략 가능(감소, 안전)
사용할 수 있는 멤버의 갯수를 조절하는 것(리모컨 변경)
기본형 형변환이 값을 변환하는 것이라면, 참조변수의 형변환은 멤버의 갯수를 조절하는 것이다.
조상﹒자손관계의 참조변수만 서로 형변환 가능 ⭐
class Car {}
class FireEngine extends Car { }
class Ambulance extends Car { }

class Car {
String color;
int door;
void drive() { // 운전하는 기능
System.out.println("drive, Brrrr~");
}
void stop() { // 멈추는 기능
System.out.println("stop!!!");
}
}
class FireEngine extends Car { // 소방차
void water() { // 물을 뿌리는 기능
System.out.println("water!!!");
}
}
FireEngine fe = new FireEngine();
Car car = (Car)fe; // OK. 조상인 Car타입으로 형변환(생략가능)
FireEngine fe2 = (FireEngine)car; // OK. 자손인 FireEngine타입으로 형변환(생략불가)
Ambulance a = (Ambulance)fe2; // 에러. 상속관계가 아닌 클래스 간의 형변환 불가
FireEngine fe = new FireEngine();

Car car = (Car)fe; // OK. 조상인 Car타입으로 형변환(생략가능)

FireEngine fe2 = (FireEngine)car; // OK. 자손인 FireEngine타입으로 형변환(생략불가)

예제7-7
참조변수가 가리키는 실제 객체가 무엇이냐가 중요
- 대부분, 형변환이 되냐 안 되냐만 따지는데
- 참조변수가 가리키는 실제 객체가 무엇이냐가 더 중요하다.
- 실제 객체의 멤버 개수가 몇개인지를 파악해야 한다.
멤버 개수가 5개라면, 5개를 넘지 않도록 하는 것이 중요하다. 5개 내에서 줄이고 늘리는 건 다음 문제이다.
코드
class Ex7_7 {
public static void main(String[] args) {
Car car = null;
FireEngine fe = new FireEngine();
FireEngine fe2 = (FireEngine)car; // 조상 -> 자손으로 형변환
Car car2 = (Car)fe2;
car2.drive(); // NullPointerException발생
...
결과
Exception in thread "main" java.lang.NullPointerException
at ch07.Ex7_7.main(Ex7_7.java:10)
코드2
class Ex7_7 {
public static void main(String[] args) {
Car car = new Car();
FireEngine fe = (FireEngine)car; // 형변환 런타임 에러. java.lang.ClassCastException
fe.water(); // 컴파일 OK.
}
}
결과2
Exception in thread "main" java.lang.ClassCastException:
class ch07.Car cannot be cast to class ch07.FireEngine
at ch07.Ex7_7.main(Ex7_7.java:6)
실제 객체의 멤버 개수가 몇개인지를 파악하는 것이 중요하고 했다.
이 예제에서 실제 객체는 Car타입이다. 따라서, Car인스턴스의 멤버는 총 4개이다.
Car인스턴스를 가리키는 참조변수 car를 FireEngine으로 형 변환을 해서 그 주소값을 FireEngine타입 참조변수 fe에 저장했다. 참조변수 fe가 사용할 수 있는 멤버 개수는 5개이다. 리모컨 버튼 개수보다 인스턴스의 멤버 개수가 더 적기 때문에 분명 잘못된 코드이다.
하지만, 컴파일러는 형 변환 연산자만 써줘서 타입만 맞으면 에러를 잡아내지 못 한다. 하지만, 실행을 해보면 ClassCastException이라는 실행 시 에러(Runtime Error)가 발생한다.


instanceof 연산자
- 참조변수의 형변환 가능 여부에 사용. 가능하면 true 반환.
- 형변환 전에 반드시 instanceof로 확인해야 함.
- (참조변수가 참조하고 있는 인스턴스의 실제 타입을 알아보기 위해 사용)
참조변수의 형변환 가능 여부에 사용. 가능하면 true 반환.
- 형변환 확인(instanceof연산자 사용)
- 형변환
형변환 전에 반드시 instanceof로 확인해야 함.
- 메서드가 호출될 때, 매개변수로 Car클래스 또는 그 자손 클래스의 인스턴스를 넘겨받겠지만 어떤 인스턴스인지 알 길이 없다.
- 그래서 instanceof 연산자로 참조변수 c가 가리키고 있는 인스턴스의 타입을 체크한다.

- 형변환을 하는 이유는 인스턴스의 원래 기능을 모두 사용하려고 하기 때문이다.
- Car타입의 리모컨인 c로는 water()를 호출할 수 없기 때문에 리모컨을 FireEngine타입으로 바꿔서 water()를 호출한다.
자주 묻는 질문 ❓
- Q1. 참조변수의 형변환은 왜 하나요?
- Q2. instanceof연산자는 언제 사용하나요?
Q. 참조변수의 형변환은 왜 하나요?
A. 참조변수(리모콘)를 변경함으로써 사용할 수 있는 멤버의 갯수를 조절하기 위해서
Q. instanceof연산자는 언제 사용하나요?
A. 참조변수를 형변환하기 전에 형변환 가능여부를 확인할 때
다형성의 장점
- 다형적 매개변수 :참조형 매개변수는 메서드 호출 시, 자신과 같은 타입 또는 자손타입의 인스턴스를 넘겨줄 수 있다.
- 하나의 배열에 여러 종류의 객체 저장 : 조상타입의 배열에 자손들의 객체를 담을 수 있다.
Product p1 = new Tv();
Product p2 = new Computer();
Product p3 = new Audio();
- 위의 코드를 Product타입의 참조변수 배열로 처리하면 아래와 같다.
Product p[] = new Product[3];
p[0] = new Tv();
p[1] = new Computer();
p[2] = new Audio();


Product[] cart = new Product[10]; // 구입한 물건을 담을 배열
void buy(Product p) {
if(money < p.price) {
System.out.println("잔액이 부족하여 물건을 살 수 없습니다.");
return;
}
money -= p.price;
bonusPoint += p.bonusPoint;
cart[i++] = p; // 제품을 Product[] cart에 저장한다.
System.out.println(p + "을/를 구입하셨습니다.");
}
Vector클래스
- 가변배열 기능(11장)
- Object[] - 모든 종류의 객체 저장 가능
🍪
📄 - 객체지향개념 암기노트 바로가기