객체지향 개념에서 다형성이란 여러 가지 형태를 가질 수 있는 능력을 의미하며, 자바에서는 한 타입의 참조 변수로 여러 타입의 객체를 참조할 수 있도록 함으로써 다형성을 프로그램적으로 구현하였다.

조상 클래스 타입의 참조 변수로 자손 클래스의 인스턴스를 참조할 수 있도록 하였다.

보통 인스턴스의 타입과 참조변수의 타입이 일치하는 것이 보통이지만, 조상 클래스와 자손 클래스처럼 상속관계에 있을 경우, 조상 클 클래스 타입의 참조 변수로 자손 클래스의 인스턴스를 참조하도록 하는 것도 가능하다.

하지만 반대로 자손타입의 참조 변수로 조상 타입의 인스턴스를 참조하는 것은 불가능하다. 

그 이유는 실제 인스턴스인 조상클래스의 멤버 개수보다 자손 타입의 참조 변수가 사용할 수 있는 멤버 개수가 더 많기 때문이다.

클래스는 상속을 통해서 확장될 수는 있어도 축소될 수는 없어서, 조상 인스턴스의 멤버 개수는 자손 인스턴스의 멤버 개수보다 항상 적거나 같다.

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

 

 

 

참조 변수의 형 변환

기본형 변수와 같이 참조 변수도 형 변환이 가능하다. 단, 서로 상속관계에 있는 클래스사이에서만 가능하기 때문에 자손타입의 참조변수를 조상타입의 참조변수로, 조상타입의 참조변수를 자손타입의 참조변수로 형변환만 가능하다.

바로 윗 조상이나 자손이 아닌, 조상의 조상으로도 형 변환이 가능하다. 따라서 모든 참조변수는 모든 클래스의 조상인 Object클래스 타입으로 형변환이 가능하다.

기본형 변수의 형 변환에서 작은 자료형에서 큰 자료형의 형 변환은 생략이 가능하듯이, 참조형 변수의 형 변환에서는 자손 타입의 참조 변수를 조상 타입으로 형 변환하는 경우에는 형 변환을 생략할 수 있다.

자손타입 > 조상타입(Up casting) : 형변환 생략가능
자손타입 < 조상타입(down castring) : 형변환 생략불가

참조 변수 간의 형 변환 역시 캐스트 연산자를 사용하며, 괄호() 안에 변환하고자 하는 타입의 이름(클래스명)을 적어주면 된다.

class Car {
	String color;
	int door;
	void drive() {
		System.out.println("drive, brrr~");
	}
	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~~");
	}
}

Car클래스는 FireEngine클래스와 Ambulance클래스의 조상이다.

하지만 FireEngine클래스와 Ambulance클래스는 서로 아무런 관계가 없다.

따라서 FireEngine클래스와 Ambulance 간의 형 변환은 불가능하다.

 

형 변환은 참조 변수의 타입을 변환하는 것이지 인스턴스를 변환하는 것은 아니기 때문에 참조변수의 형변환은 인스턴스에 아무런 영향을 미치지 않는다.

 단지 참조변수의 형 변환을 통해서, 참조하고 있는 인스턴스에서 사용할 수 있는 멤버의 범위(개수)를 조절하는 것뿐이다.

서로 상속관계에 있는 타입간의 형변환은 양방향으로 자유롭게 수행될 수 있으나, 
참조변수가 가리키는 인스턴스의 자손타입으로 형변환은 허용되지 않는다.
그래서 참조변수가 가리키는 인스턴스의 타입이 무엇인지 확인하는 것이 중요하다.

 

 

instanceof연산자

참조 변수가 참조하고 있는 인스턴스의 실재 타입을 알아보기 위해 instanceof연산자를 사용한다. 주로 조건문에 사용되며, instanceof의 왼쪽에는 참조 변수를 오른쪽에는 타입(클래스명)이 피연산자로 위치한다. 그리고 연산의 결과로 boolean값인 true와 false중의 하나를 반환한다.

instanceof를 이용한 연산결과로 true가 나왔다면 참조변수가 검사한 타입으로 형변환이 가능하다는 것을 의미한다.

값이 null인 참조변수에 대해 instanceof연산을 수행하면 false를 결과로 얻는다.

public class InstanceofTest {
	public static void main(String[] args) {
		FireEngine fe = new FireEngine();

		if (fe instanceof FireEngine) {
			System.out.println("This is a FireEngine instance.");
		}
		if (fe instanceof Car) {
			System.out.println("This is a Car instance.");
		}
		if (fe instanceof Object) {
			System.out.println("This is a Object instance.");
		}
		System.out.println(fe.getClass().getName());
		System.out.println(fe.getClass().getSimpleName());
	}
}
This is a FireEngine instance.
This is a Car instance.
This is a Object instance.
a210719.FireEngine
FireEngine

실제 인스턴스와 같은 타입의 instanceof연산 이외에 조상타입의 instanceof연산에도 true를 결과로 얻으며, instanceof연산의 결과가 true라는 것은 검사한 타입으로의 형변환을 해도 아무런 문제가 없다.

어떤 타입에 대한 instanceof연산의 결과가 true라는 것은 검사한 타입으로 형변환이 가능하다는 것을 뜻한다.

 

 

 

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

조상타입의 참조변수로 자손 인스턴스를 참조하는 경우와 자손타입의 참조변수로 자손 인스턴스를 참조하는 경우는 다른 결과를 얻는다.

메서드의 경우 조상 클래스의 메서드를 자손의 클래스에서 오버라이딩한 경우에도 참조변수의 타입에 관계없이 항상 실제 인스턴스의 메서드(오버라이딩된 메서드)가 호출되지만, 멤버변수의 경우 참조변수의 타입에 따라 달라진다.

static메서드는 static변수처럼 참조변수의 타입에 영향을 받는다. 참조변수의 타입에 영향을 받지 않는 것은 인스턴스메서드 뿐이다. 그래서 static메서드는 반드시 참조변수가 아닌 클래스이름.메서드()로 호출해야 한다.

 

멤버변수가 조상 클래스와 자손 클래스에 중복으로 정의된 경우, 조상타입의 참조변수를 사용했을 때는 조상 클래스에 선언된 멤버변수가 사용되고, 자손타입의 참조변수를 사용했을 때는 자손 클래스에 선언된 멤버변수가 사용된다.

 중복 저의되지 않은 경우, 조상타입의 참조변수를 사용했을 때와 자손타입의 참조변수를 사용했을 때의 차이는 없다.

 

 

 

매개변수의 다형성

참조변수의 다형적인 특징은 매개변수에도 적용된다. 

class Product{
	int price;
	int bonusPoint;
	
	Product(int price) {
		this.price = price;
		bonusPoint = (int)(price/10.0);
	}
}
class Tv extends Product {
	Tv() {
		super(100);
	}
	public String toString() { return "Tv"; }
}
class Computer extends Product {
	Computer() { super(200); }
	
	public String toString() { return "Computer"; }
}

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 PolyArgumentTest {
	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 + "점입니다.");
		
	}
}
Tv을/를 구입하셨습니다.
Computer을/를 구입하셨습니다.
현재 남은 돈은 700만원입니다.
현재 보너스 점수는30점입니다.

고객(Buyer)이 buy(Product p)메서드를 이용해서 Tv와 Computer를 구입하고, 고객의 잔고와 보너스점수를 출력하는 예제이다.

 

 

 

여러 종류의 객체를 배열로 다루기

조상타입의 참조변수 배열을 사용하면, 공통의 조상을 가진 서로 다른 종류의 객체를 배열로 묶어서 다룰 수 있다. 또는 묶어서 다루고싶은 객체드르이 상속관계를 따져서 가장 가까운 공통조상 클래스 타입의 참조변수 배열을 생성해서 객체들을 저장하면 된다.

import java.util.*;
	class Product {
		int price;
		int bonusPoint;
		
		Product(int price) {
			this.price = price;
			bonusPoint = (int)(price/10.0);
		}
		
		Product() {
			price = 0;
			bonusPoint = 0;
		}
	}



class Tv extends Product {
	Tv() { super(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;
	Vector<Product> item = new Vector<Product>();
	
	void buy(Product p) {
		if(money < p.price) {
			System.out.println("잔액이 부족하여 물건을 살 수 없습니다.");
			return;
		}
		money -= p.price;
		bonusPoint += p.bonusPoint;
		item.add(p);
		System.out.println(p + "을/를 구입하셨습니다.");
	}
	
	void refund(Product p) {
		if(item.remove(p)) {
			bonusPoint -= p.bonusPoint;
			System.out.println(p + "을/를 반품하셨습니다.");
		} else { System.out.println("구입하신 제품 중 해당 제품이 없습니다.");
	}
}
	
void summary() {
	int sum = 0;
	String itemList ="";
	
	if(item.isEmpty()) {
		System.out.println("구입하신 제품이 없습니다.");
		return;
	}
	
	for(int i = 0; i < item.size();i++) {
		Product p = (Product)item.get(i);
		sum += p.price;
		itemList += (i==0) ? "" + p : ", " + p;
	}
	System.out.println("구입하신 물품의 총금액은 " + sum + "만원입니다.");
	System.out.println("구입하신 제품은 "+ itemList + "입니다.");
}
}

public class PolyArgumentTest3 {
	public static void main(String[] args) {
		Buyer b = new Buyer();
		Tv tv = new Tv();
		Computer com = new Computer();
		Audio audio = new Audio();

		b.buy(tv);
		b.buy(com);
		b.buy(audio);
		b.summary();
		System.out.println();
		b.refund(com);
		b.summary();
	}
}
Tv을/를 구입하셨습니다.
computer을/를 구입하셨습니다.
Audio을/를 구입하셨습니다.
구입하신 물품의 총금액은 350만원입니다.
구입하신 제품은 Tv, computer, Audio입니다.

computer을/를 반품하셨습니다.
구입하신 물품의 총금액은 150만원입니다.
구입하신 제품은 Tv, Audio입니다.

문자여과 참조변수의 덧셈(결합연산)은 참조변수에 toString()을 호출해서 문자열을 얻어 결합한다. 그래서 위 예제에 나오는 "+p"는 +p.toString()이 되고, 만일 p.toString()의 결과가 Audio라면 +Audio가 되어 Audio가 추가된다.

+ Recent posts