본문 바로가기

JAVA

7장_2

9. 다형성

조상 타입 참조 변수로 자손 타입 객체를 다루는 것

SmartTv s = new SmartTv(); // 참조 변수와 인스턴스의 타입이 일치
Tv t = new SmartTv(); // 조상 타입 참조 변수로 자손 타입 인스턴스 참조 -> 다형성

 

위의 두 선언의 차이는?

일반TV는 멤버 5개를 가지고 있다.

스마트TV는 일반TV를 상속해서 일반TV꺼 5개 + 본인꺼 2개 총 7개를 가지고 있다.

첫 번째 선언은 스마트TV 리모컨으로 스마트TV의 7가지 기능을 모두 사용할 수 있다.

두 번째 선언은 일반TV 리모컨으로 스마트TV의 7가지 기능 중, 일반TV가 가진 5가지 기능만 사용할 수 있다.

 

더 적은 멤버를 사용 가능한데 왜! 다형성이 장점이 될 수 있을까?

 

 

반대로, 자손 타입 참조 변수로는 조상 타입의 객체를 다룰 수 없다

 

SmartTv s = new Tv(); // 에러

 

왜 에러가 날까?

스마트TV는 7개의 멤버를 가지고 있다. (일반TV는 5개의 멤버)

예를 들어, 스마트TV 리모컨에는 일반TV에 없는 넷플릭스 기능을 가지고 있는거야.

고객이 스마트TV 리모컨으로 넷플릭스를 보려고 그 버튼을 눌렀어.

근데 에러가 뜨는 거야! 왜?

넷플릭스 버튼이 리모컨에는 있지만 실제 참조하는 객체는 일반TV라서 넷플릭스 기능을 가지고 있지 않거든.

그래서 자식으로는 조상을 다룰 수 없다!

 

 

10. 참조변수의 형변환

cf) 기본형의 형변환은 값이 바뀐다.

3.6 -> int형으로 변환 -> 3

 

★조상과 자손의 참조변수를 형변환을 한다는 것은

'사용할 수 있는 멤버의 개수를 줄였다 늘였다 마음대로 조절하는 것'

 

조상과 자손 관계의 참조변수는 서로 형변환이 가능하다.

왜 형변환 한다고? 사용할 수 있는 멤버를 줄였다가 늘였다가 하려고!

 

class Car { }
class FireEngine extends Car { }
class Ambulance  extends Car { }


FireEngine f = new FireEngine();

Car c = (Car)f; 	       // 조상인 Car 타입으로 형변환
FireEngine f2 = (FireEngine)c; // 자손인 FireEngine 타입으로 형변환

Ambulance a = (Ambulance)f;    // 에러. 상속관계가 아닌 클래스 간의 형변환 불가

 

참조변수의 형변환은 그저 리모컨을 다른 종류의 것으로 바꾸는 것뿐이다.

리모컨의 타입을 바꾸는 이유는 사용할 수 있는 멤버 개수를 조절하기 위한 것이고,

 

public class Main {

	public static void main(String[] args) {
		
		Car car = null;
		FireEngine fe = new FireEngine();
		FireEngine fe2 = null;
		
		fe.water(); // water-
		
		car = (Car)fe; // car = fe; -> 형변환 생략 가능 -> 왜? 조상 타입으로 자손 타입 다루는건 안전
		//car.water(); // Car 타입으로는 자식을 다룰 수 없음
		
		fe2 = (FireEngine)car; // 형변환 생략 불가 -> 왜? 자손타입으로 조상 타입 다루는건 위험하기 때문
		fe2.water(); // water-
		
	}

}

class Car {
	String color;
	int door;
	
	void drive() {
		System.out.println("drive-");
	}
	
	void stop() {
		System.out.println("stop-");
	}
}

class FireEngine extends Car {
	void water() {
		System.out.println("water-");
	}
}

 

참조변수 fe로는 5개의 멤버 사용 가능, 참조변수 car로는 4개의 멤버 사용 가능, 참조변수 fe2로는 5개의 멤버 사용 가능

조상과 자식 간에는 아주 유연하고 자유롭게 형변환이 가능하다는 것을 알 수 있지!

 

 

**주의!

객체가 없어도 형변환에는 문제가 없다. 심지어 호출도 가능하다.

하지만 실행하면 에러가 발생한다.

컴파일러는 객체가 있든지 말든지 형변환만 맞게 해 주면 깜빡 속는다. 그래서 컴파일 에러가 안 난다. (바보)

 

즉, 형변환에서는 실제 인스턴스가 무엇인지가 중요하다.

(= 실제 가리키고 있는 객체가 무엇인지가 중요하다 = 가리키는 객체가 new 되어있는지가 중요하다)

 

public class Main {
	public static void main(String[] args) {
		Car c = new Car();
		FireEngine fe = (FireEngine)c; // 형변환 실행 에러. 왜? 실제 가리키는 객체가 Car잖아.
		fe.water(); // 컴파일 OK. 하지만 실행하면 ClassCastException 발생.
	}
}

 

 

 

10. instanceof 연산자

참조변수의 형변환 가능 여부 확인에 사용

형변환하기 전에는 반드시 instanceof로 꼭 형변환 가능 여부 확인

 

// 매개변수가 Car 타입으로 Car, FireEngine, Ambulance 모두 들어올 수 있음
void doWork(Car c) {
	if(c instanceof FireEngine) { // c가 FireEngine 타입이니? (Car 혹은 FireEngine이니?)
    		FireEngine fe = (FireEngine)c; // 들어온 타입을 FireEngine으로 형변환
        	fe.water();
    	}
	else if(c instanceof Ambulance) {
    		Ambulance a = (Ambulance)c;
    	}
}

 

자손과 조상 간에는 쌍방으로 형변환이 가능하다. 따라서 instanceof의 값이 true가 나온다.

 

FireEngine fe = new FireEngine();

System.out.println(fe instanceof Object); // true
System.out.println(fe instanceof Car); // true
System.out.println(fe instanceof FireEngine); // true

 

 

11. 다형성의 장점 - 매개변수의 다형성

정말 중요, 반복해서 이해하자.

메서드의 매개변수로 조상 타입의 참조변수를 사용해서

하나의 메서드로 여러 타입의 객체(자손)를 받는 것

 

이게 무슨 말이냐? 아래 예제를 살펴보자.

 

class Product {
	int price;
	int bonusPoint;
}

class Tv extends Product {}
class Computer extends Product {}
class Audio extends Product {}

class Buyer {
	int money = 1000;
	int bonusPoint = 0;
}

// 다형성이 없다면~
// 상품을 구매할 때마다 하나씩 다 메서드를 만들어줘야함
void buy(Tv t) {
	money -= p.price;
	bonusPoint += p.bonusPoint;
}
void buy(Computer c) {
	money -= p.price;
	bonusPoint += p.bonusPoint;
}
void buy(Audio a) {
	money -= p.price;
	bonusPoint += p.bonusPoint;
}

// 다형성이 있다면~
// 조상 타입으로 모든 자식 타입을 다룰 수 있게됨
void buy(Product p) { // 매개변수에 Tv, Computer, Audio, Phone, Tablet... 모두 가능
	money -= p.price;
	bonusPoint += p.bonusPoint;
}

 

그렇다면 이번에는 실제 예제를 살펴보자.

package java7;

class Product {
	int price;
	int bonusPoint;	
	
	Product(int price) {
		this.price = price;
		bonusPoint = (int)(price/10.0);
	}
}

class Tv extends Product {
	Tv(int price) {
		super(price);
	}

	public String toString() {
		return "Tv";
	}
}

class Computer extends Product {
	Computer(int price) {
		super(price);
	}

	public String toString() {
		return "Computer";
	}
}

class Buyer {
	int money = 1000;
	int bonusPoint = 0;
	
	// Product를 상속한 클래스는 모두 들어올 수 있으며 이 안에서만 p라는 변수로 다뤄짐
	void buy(Product p) { 
		if(money < p.price) {
			System.out.println("잔액 부족");
			return;
		}
		money -= p.price;
		bonusPoint += p.bonusPoint;
		System.out.println(p + " 구매 완료");
	}
}

public class Main {

	public static void main(String[] args) {
		
//		Buyer b = new Buyer();
//		b.buy(new Tv());
//		b.buy(new Computer());
		
		Buyer b = new Buyer();
		Tv t = new Tv(100); // Product t = new Tv(100)도 가능
		Computer c = new Computer(200); // Product t = new Computer(200)도 가능
		
		// 조상 타입으로 만들어진 메서드를 자식들이 사용하는 것을 볼 수 있음!
		b.buy(t); // Tv 구매 완료
		b.buy(c); // Computer 구매 완료
		
		System.out.println("현재 잔액: " + b.money); // 현재 잔액: 700
		System.out.println("현재 포인트: " + b.bonusPoint); // 현재 포인트: 30
		
	}

}

 

12. 다형성의 장점 - 하나의 배열에 여러 종류의 객체 다루기


정말 중요, 반복해서 이해하자.

하나의 배열에 여러 종류의 객체를 저장할 수 있다.

이게 무슨 말이냐?
Product 타입의 배열이 있다. 그 배열에는 Tv 타입, Computer 타입, Audio 타입 모두 들어갈 수 있다.

즉, Product 타입에 Product 타입만 들어올 수 있는 것이 아니라 

Product를 상속한 자손들(Tv, Computer, Audio)은 모두 들어올 수 있다는 뜻이다.

Product[] p = new Product[3];

p[0] = new Tv();
p[1] = new Computer();
p[2] = new Audio();

 

 

class Product {
	int price;
	int bonusPoint;	
	
	Product(int price) {
		this.price = price;
		bonusPoint = (int)(price/10.0);
	}
}

class Tv extends Product {
	Tv(int price) {
		super(price);
	}

	public String toString() {
		return "Tv";
	}
}

class Computer extends Product {
	Computer(int price) {
		super(price);
	}

	public String toString() {
		return "Computer";
	}
}

class Audio extends Product {
	Audio(int price) {
		super(price);
	}
	
	public String toString() {
		return "Audio";
	}
}

class Buyer {
	int money = 1000;
	int bonusPoint = 0;
	
	Product[] cart = new Product[10];
	int i = 0; // 물건의 개수
	
	void buy(Product p) { 
		if(money < p.price) {
			System.out.println("잔액 부족");
			return;
		}
		money -= p.price;
		bonusPoint += p.bonusPoint;
		cart[i++] = p;
		System.out.println(p + " 구매 완료");
	}
	
	void summary() {
		int sum = 0;
		String itemList = "";
		
		for(int i = 0; i < cart.length; i++) {
			if(cart[i] == null) break;
			
			sum += cart[i].price;
			itemList += cart[i] + ", ";
		}
		
		System.out.println("구매 금액: " + sum);
		System.out.println("구입 제품: " + itemList);
	}
}

public class Main {
	public static void main(String[] args) {
		Buyer b = new Buyer();
		b.buy(new Tv(100));
		b.buy(new Computer(200));
		b.buy(new Audio(50));
		b.summary();
	}
}
/*
Tv 구매 완료
Computer 구매 완료
Audio 구매 완료
구매 금액: 350
구입 제품: Tv, Computer, Audio,
*/

'JAVA' 카테고리의 다른 글

7장_3  (0) 2021.01.22
7장_1  (0) 2021.01.22
쓰레드  (0) 2021.01.20
다형성 / 동적 바인딩 / 인터페이스 / 추상클래스  (0) 2021.01.18
static은 언제 붙일까?  (0) 2021.01.14