Java/객체지향

[객체지향][자바의 정석] - 7.5 다형성

얄루몬 2021. 11. 15. 19:09

7.5 다형성

 

1. 다형성(Polymorphism)이란? (1)

- 여러 가지 형태를 가질 수 있는 능력을 의미한다.

- 하나의 참조변수로 여러 타입의 객체를 참조할 수 있는 것을 의미한다. 즉, 조상타입의 참조변수로 자손타입의 객체를 다룰 수 있는 것이 다형성이다.

package javaStandard;

class Tv{
	boolean power; //전원상태(on/off)
	int channel; //채널
	
	void power() {
		power = !power;
	}
	void channelUp() {
		++channel;
	}
	void channelUDown() {
		--channel;
	}
}

class CaptionTv extends Tv{
	String text; //캡션내용
	void caption() {
		// 내용생략 
	}
}

 

CaptionTv c = new CaptionTv(); //참조 변수와 인스턴스의 타입이 일치
Tv t = new CaptionTv(); // 조상 타입 참조변수로 자손 타입 인스턴스 참조
//타입이 일치하는 경우는 원래 OK

 

Tv t = new Tv();
CaptionTv c = new CaptionTv();

Tv t = new CaptionTv();
//타입이 불일치하는 경우에도 OK ---> 다형성!!!!
//조상타입이 참조변수도 자손타입이 실제 객체

참조변수(조상)와 실제 객체(자손)의 타입이 불일치하는 경우 - 다형성은 이를 허용한다

타입이 불일치하는 경우의 장점(2가지)가 있다.

 

 

1. 다형성(Polymorphism)이란? (2)

기능의 일부만 사용할 수 있는 것이 어떻게 다형성의 장점이 될 수 있을까? 

일단은 참조변수 타입과 인스턴트 타입이 다를 때 어떤지를 확실히 알아두기로 하자

 

1. 다형성(Polymorphism)이란? (3)

- 조상타입의 참조변수로 자손타입의 인스턴스를 참조할 수 있지만, 반대로 자손타입의 참조변수로 조상타입의 인스턴스를 참조할 수는 없다.

Tv t = new CaptionTv(); //허용 / 조상타입의 참조변수로 자손 타입의 객체를 가리키는 경우
CaptionTv c = new Tv(); // 에러 --> 허용 안 됨 / 자손타입의 참조변수로 조상 타입의 객체를 가리키는 경우

 

2. 참조변수의 형변환

- 서로 상속관계에 있는 타입간의 형변환만 가능하다.

- 자손 타입에서 조상타입으로 형변환하는 경우, 형변환 생략이 가능하다.

자손타입 -> 조상타입 (Up - Casting) : 형변환 생략가능
조상타입 -> 자손타입 (Down - Casting) : 형변환 생략불가

package javaStandard;

class Car{
	String color;
	int door;
	
	void drive() {
		System.out.println("drive, Brrrrr~");
	}
	void stop() {
		System.out.println("Stop!!!");
	}
	class FireEngine extends Car{
		void water() {
			System.out.println("Water!!!");
		}
	}
	class Ambulance extends Car {
		void siren() {
			System.out.println("siren~~");
		}
	}
}

결론 : 사용할 수 있는 멤버의 갯수를 조절하는 것이다. / 기본형 형변환처럼 값이 달라지는 게 아니다. 멤버의 갯수만 바뀐다. 

 

- 조상 자손 관계는 형변환이 가능하지만 형제 관계는 형변환이 불가하다.

package javajungsuk;

public class Ex_7_7 {
	public static void main(String[] args) {
		Car car = null;
		FireEngine fe = new FireEngine();
		FireEngine fe2 = null;
		
		fe.water();
		car = fe; //car = (Car)fe; 형변환 생략
		//car.water ... 컴파일 에러 Car타입의 참조변수로는 water()를 호출할 수 없다
		fe2 = (FireEngine)car; // 조상타입 -> 자손타입 형변환 생략불가
		fe2.water()
	}
}

package javajungsuk;

public class Ex_7_7 {
	public static void main(String[] args) {
		Car car = null;
		FireEngine fe = new FireEngine();
		FireEngine fe2 = null;
		
		fe.water();
		car = fe;
		//car.water ... 컴파일 에러 Car타입의 참조변수로는 water()를 호출할 수 없다
		fe2 = (FireEngine)car; // 조상타입 -> 자손타입 형변환 생략불가
		fe2.water()
	}
}

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!!!");
		}
	}
}

<Null을 가리키고 있을 때>

package javajungsuk;

public class Ex7_7_2 {
	public static void main(String[] args) {
		Car car = null;
		FireEngine fe = null; //실제 인스턴스가 무엇인지가 중요하다.
		
		FireEngine fe2 = (FireEngine)car; // 조상 -> 자손으로 형변환
		Car car2 = (Car)fe2; // 자손 - > 조상으로 형변환
		car2.drive(); // NullPointerException 발생 참조변수가 

	}
}

 

<참조변수가 가르키고 있는 인스턴스(객체)가 중요한 예시>

package javajungsuk;


public class Ex7_7_2 {
	public static void main(String[] args) {
		Car c = new Car();
		FireEngine fe = (FireEngine)c; //형변환 에러 java.lang.ClassCastException
		fe.water();//컴파일은 가능 ClassCastException 발생!!
		//실제 가르키고 있는 객체는 Car 이기 때문에 4개의 기능만 사용가능해서 
		//water()는 사용 불가
	}
}
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!!!");
	}
}

 

3. instanceof 연산자

- 참조변수의 형변환 가능여부 확인에 사용한다. 가능하다면 True를 반환한다.

  1. 형변환 해도 되는지를 확인한 뒤
  2. 형변환을 한다

- 형변환 전에 반드시 instanceof()를 확인하고 진행해야 한다.

void dowork(Car c){
    if(c instanceof FireEngine) {
        FireEngine fe = (FireEngine)c;
        fe.water();
    }
    else if (c instanceof Ambulance){
        Ambulance a = (Ambulance)c;
    }
}

 

<상속계층도를 이용해서 알아보는 instanceof 연산자>

void dowork(Car c){
    if(c instanceof FireEngine) {
        FireEngine fe = (FireEngine)c;
        fe.water();
    }
    else if (c instanceof Ambulance){
        Ambulance a = (Ambulance)c;
    }
}

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

형변환을 했을 때 객체가 달라지는 것이 아니고 참조변수의 값도 그대로고 타입만 달라지는 것이다. 

 

4. 참조변수와 인스턴스변수의 연결

- 멤버변수가 중복정의된 경우, 참조변수의 타입에 따라 연결되는 멤버변수가 달라진다. (참조변수타입에 영향받음)

- 메서드가 중복정의된 경우, 참조변수의 타입에 관계 없이 항상 실제 인스턴스의 타입에 정의된 메서드가 호출된다. (참조변수 타입에 영향을 받지 않는다.)

 

 

5. 매개변수의 다형성

- 참조형 매개변수는 메서드 호출시, 자신과 같은 타입 또는 자손타입의 인스턴스를 넘겨줄 수 있다.

package javajungsuk;


//부모
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 -= c.price;
    bonusPoint += c.bonusPoint;
}
void buy(Computer c) {
    money -= c.price;
    bonusPoint += c.bonusPoint;
}
void buy(Audio c) {
    money -= c.price;
    bonusPoint += c.bonusPoint;
}

package javajungsuk;


//부모
class Product{
	int price;//제품가격
	int bonusPoint;//보너스점수
	
	Product(int price){
		this.price = price;
		bonusPoint = (int)(price/10.0); //보너스 점수는 제품 가격의 10%
	}
}


//자손
class Tv extends Product {
	Tv(){
		//조상 클래스의 생성자 Product(int price)를 호출
		super(100); //Tv 가격 100만원으로 설정
	}
	public String toString() {
		return "Tv";
		}
}
class Computer extends Product {
	Computer() {
		super(200);
	}
	public String toString() { 
		return "Computer"; 
		}
}
class Audio extends Product {
	Audio(){ 
		super(50); 
	}
	public String toString() {
		return "Audio";
	}
}


class Buyer { //물건을 사는 사람
	int money = 1000; //소유금액
	int bonusPoint = 0; //보너스점수
	
	void buy(Product p) {
		if (money < p.price) {
			System.out.println("잔액이 부족하여 물건을 살 수 없습니다.");
			return;
		}
		
		money -= p.price;
		bonusPoint += p.bonusPoint;
		System.out.println(p + "을/를 구입하셨습니다.");
	}
}

class java7_5_5{
	public static void main(String args[]) {
		Buyer b = new Buyer();
		
		b.buy(new Tv());
		b.buy(new Computer());
		
		System.out.println("현재 남은 돈은"+b.money+"만원 입니다.");
		System.out.println("현재 보너스 점수는"+b.bonusPoint+"점입니다.");
	}
	
}

 

6. 여러 종류의 객체를 하나의 배열로 다루기

- 조상 타입의 배열에 자손들의 객체를 담을 수 있다.

- 하나의 배열에 여러종류 객체 저장이 가능한 것이 다형성의 장점 중 하나다.

 

product p1 = new Tv();
product p2 = new Computer();
product p3 = new Audio();

변수를 개별적으로 쓴 경우

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

변수를 묶어서 쓴 경우

다형성때문에 product 객체뿐만 아니고 자식들이 다 들어갈 수 있다.

 

package javajungsuk;


//부모
class Product2{
	int price;//제품가격
	int bonusPoint;//보너스점수
	
	Product2(int price){
		this.price = price;
		bonusPoint = (int)(price/10.0); //보너스 점수는 제품 가격의 10%
	}
}


//자손
class Tv2 extends Product2 {
	Tv2(){
		//조상 클래스의 생성자 Product2(int price)를 호출
		super(100); //Tv 가격 100만원으로 설정
	}
	public String toString() {
		return "Tv";
		}
}
class Computer2 extends Product2 {
	Computer2() {
		super(200);
	}
	public String toString() { 
		return "Computer"; 
		}
}
class Audio2 extends Product2 {
	Audio2(){ 
		super(50); 
	}
	public String toString() {
		return "Audio";
	}
}

class Buyer2 { //물건을 사는 사람
	int money = 1000; //소유금액
	int bonusPoint = 0; //보너스점수
	Product2[] cart = new Product2[10]; //구입제품 저장하기 위한 배열
	int i = 0; // Product 배열에 사용될 카운터
	
	
	void buy(Product2 p) {
		if (money < p.price) {
			System.out.println("잔액이 부족하여 물건을 살 수 없습니다.");
			return;
		}
		
		money -= p.price;
		bonusPoint += p.bonusPoint;
		cart[i++] = p;
		System.out.println(p.toString() + "을/를 구입하셨습니다.");
	}
	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 +"입니다.");
		}
}


class java7_6_1{
	public static void main(String args[]) {
		Buyer2 b = new Buyer2();
		
		b.buy(new Tv2());
		b.buy(new Computer2());
		b.buy(new Audio2());
		b.summary();
		
	}
	
}
Tv을/를 구입하셨습니다.
Computer을/를 구입하셨습니다.
Audio을/를 구입하셨습니다.
구입하신 물품의 총금액은350만원입니다.
구입하신 제품은Tv, Computer, Audio, 입니다.

 

 

※ 다형성이 자바의 꽃이기 때문에 이를 확실히 이해하지 못하면 넘어가지 말 것 ~

 

 

정리

Q1. 참조변수 타입은 인스턴스의 타입과 반드시 일치해야 하나요?

A1. 아니다 일치하는 것이 보통이지만 일치하지 않을 수도 있다.

 

Q2. 참조변수가 조상타입일 때와 자손타입일 때의 차이?

A2. 참조변수로 사용할 수 있는 멤버의 갯수가 달라진다.

 

Q3. 자손 타입의 참조변수로 조상 타입의 객체를 가리킬 수 있나요? 

A3. 허용되지 않는다.