다형성
여러 가지 형태를 가질 수 있는 능력, 조상클래스 타입의 참조변수로 자손클래스의 인스턴스를 참조할 수 있도록 한다.
CaptionTv c = new CaptionTv();
Tv t = new CatptionTv();
참조변수 t로는 CaptionTv인스턴스의 모든 멤버를 사용할 수 없다. Tv타입의 참조변수로는 CaptionTv인스턴스 중에서 Tv클래스의 멤버들만 사용할 수 있다.
- 조상타입의 참조변수로 자손타입의 인스턴스를 참조할 수 있다.
- 반대로 자손타입의 참조변수로 조상타입의 인스턴스를 참조할 수는 없다.
참조변수의 형변환
기본형 변수와 같이 참조변수도 형변환이 가능하다. 단, 서로 상속관계에 있는 클래스 사이에서만 가능하기 때문에 자손타입의 참조변수를 조상타입의 참조변수로, 조상타입의 참조변수를 자손타입의 참조변수로의 형변환만 가능하다.
자손타입 → 조상타입(Up-casting) : 형변환 생략가능
자손타입 ← 조상타이(Down-casting) : 형변환 생략 불가
조상타입의 참조변수를 자손타입의 참조변수로 변환하는 것을 다운캐스팅, 자손타입의 참조변수를 조상타입의 참조변수로 변환하는 것을 업캐스팅이라 한다.
class Car{
...
}
class FireEngine extends Car{
...
}
class Ambulance extends Car{
...
}
Car클래스는 FireEngine클래스와 Ambulance클래스의 조상이다. FireEngine과 Ambulane는 형제관계는 아니다. 따라서, Car타입의 참조변수와 FireEngine타입의 참조변수 그리고 Car타입의 참조변수와 Ambulance타입의 참조변수 간에는 서로 형변환이 가능하지만, FireEngine타입의 참조변수와 Ambulance타입의 참조변수 간에는 서로 형변환이 가능하지않다.
Car타입의 참조변수를 c를 Car타입의 조상인 Object타입의 참조변수로 형변환 하는 것은 참조변수가 다룰 수 있는 멤버의 개수가 실제 인스턴스가 갖고 있는 멤버의 개수보다 적을 것이 분명하므로 문제가 되지 않는다. 그래서 형변환이 생략이 가능하다.
형변환은 참조변수의 타입을 변환하는 것이지 인스턴스를 변환하는 것은 아니기 때문에 참조변수의 형변환은 인스턴스에 아무런 영향을 미치지 않는다. 단지 참조변수의 형변환을 통해서 참조하고 있는 인스턴스에서 사용할 수 있는 멤버의 범위(개수)를 조절하는 것 뿐이다.
package object_oriented_programming2;
public class CastingTest1 {
public static void main(String[] args) {
Car car = null;
FireEngine fe = new FireEngine();
FireEngine fe2 = null;
fe.water();
car = fe;
fe2 = (FireEngine) car;
fe2.water();
}
}
class Car{
String color;
int door;
void drive() {
System.out.println("dirce, Brrr");
}
void stop() {
System.out.println("Stop!!");
}
}
class FireEngine extends Car{
void water() {
System.out.println("Water!");
}
}
//결과
Water!
Water!
- Car car = null;
- Car타입의 참조변수 car를 선언하고 null로 초기화한다.
- FireEngine fe =new FireEngine();
- FireEngine인스턴스를 생성하고 FireEngine타입의 참조변수가 참조하도록 한다.
- car = fe; 조상타입 ← 자손타입
- 참조변수 fe가 참조하고있는 인스턴스를 참조변수 car가 참조하도록 한다.
- fe2 = (FireEngine)car; 자손타입 ← 조상타입
- 참조변수 car가 참조하고있는 인스턴스를 참조변수 fe2가 참조하도록한다.
package object_oriented_programming2;
public class CastingTest2 {
public static void main(String[] args) {
Car car = new Car();
Car car2 = null;
FireEngine fe = null;
car.drive();
fe = (FireEngine)car;
fe.drive();
car2 = fe;
car2.drive();
}
}
//결과
Exception in thread "main" dirce, Brrr
java.lang.ClassCastException: class object_oriented_programming2.Car cannot be cast to class object_oriented_programming2.FireEngine (object_oriented_programming2.Car and object_oriented_programming2.FireEngine are in unnamed module of loader 'app')
at object_oriented_programming2.CastingTest2.main(CastingTest2.java:11)
조상타입의 인스턴스를 자손타입의 참조변수로 참조하는 것은 허용되지 않는다.
참조변수가 가리키는 인스턴스의 자손타입으로 형변환은 허용되지 않는다. 그래서 참조변수가 가리키는 인스턴스의 타입이 무엇인지 확인하는 것이 중요하다.
instanceof연산자
참조변수가 참조하고 있는 인스턴스의 실제 타입을 알아보기 위해 instanceof연산자를 사용한다.
void doWork(Car c){
if(c instanceof FireEngine) {
FireEngine fe = (FireEngine)c;
...
}else if (c instanceof Ambulance) {
Ambulance a = (Ambulance)c;
}
실제 인스턴스와 같은 타입의 참조변수로 형변환을 해야만 인스턴스의 모든 멤버들을 사용할 수 있다.
package object_oriented_programming2;
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 an Object instance.");
}
System.out.println(fe.getClass().getName()); //클래스의 이름을 출력
}
}
//결과
this is a FireEngine instance.
this is a Car instance.
this is a an Object instance.
object_oriented_programming2.FireEngine
instatnceof연산의 결과가 true라는 것은 검사한 타입으로 형변환이 가능하다는 것을 뜻한다.
참조변수와 인스턴스의 연결
멤버변수의 경우 참조변수의 타입에 따라 달라진다.
package object_oriented_programming2;
public class BindingTest {
public static void main(String[] args) {
Parent3 p = new Child3();
Child3 c = new Child3();
System.out.println("p.x = "+p.x);
p.method();
System.out.println("c.x = "+c.x);
c.method();
}
}
class Parent3{
int x = 100;
void method() {
System.out.println("Parent Method");
}
}
class Child3 extends Parent3{
int x = 200;
void method() {
System.out.println("Child Method");
}
}
//결과
p.x = 100
Child Method
c.x = 200
Child Method
타입은 다르지만 참조변수 p와 c모두 child인스턴스를 참조하고 있다.
package object_oriented_programming2;
public class BindingTest2 {
public static void main(String[] args) {
Parent4 p = new Child4();
Child4 c = new Child4();
System.out.println("p.x = "+p.x);
p.method();
System.out.println("c.x = "+c.x);
c.method();
}
}
class Parent4{
int x = 100;
void method() {
System.out.println("Parent Method");
}
}
class Child4 extends Parent4{}
child클래스에는 아무런 멤버도 정의되어 있지 않고 단순히 조상으로부터 멤버들을 상속받기에 참조변수의 타입에 관계없이 조상의 멤버들을 사용하게된다.
package object_oriented_programming2;
public class BindingTest3 {
public static void main(String[] args) {
Parent5 p = new Child5();
Child5 c = new Child5();
System.out.println("p.x = "+p.x);
p.method();
System.out.println("c.x = "+c.x);
c.method();
}
}
class Parent5{
int x = 100;
void method() {
System.out.println("Parent Method");
}
}
class Child5 extends Parent5{
int x = 200;
void method() {
System.out.println("x="+x);
System.out.println("super.x="+super.x);
System.out.println("this.x="+this.x);
}
}
//결과
p.x = 100
x=200
super.x=100
this.x=200
c.x = 200
x=200
super.x=100
this.x=200
매개변수의 다형성
참조변수의 다형적인 특징은 메서드의 매개변수에도 적용된다.
package object_oriented_programming2;
class Product{
int price;
int bonusPoint;
Product(int price){
this.price = price;
bonusPoint =(int)(price/10.0);
}
}
class Tv3 extends Product{
Tv3(){
//조상클래스의 생성자 Product(int price)를 호출한다
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+"을/를 구입하였습니다.");
}
}
public class PolyArgumentTest {
public static void main(String[] args) {
Buyer b = new Buyer();
b.buy(new Tv3());
b.buy(new Computer());
System.out.println("현재 남은 돈은 "+b.money+"만원 입니다.");
System.out.println("현재 보너스점수는 "+b.bonusPoint+"점 입니다.");
}
}
//결과
Tv을/를 구입하였습니다.
Computer을/를 구입하였습니다.
현재 남은 돈은 700만원 입니다.
현재 보너스점수는 30점 입니다.
여러 종류의 객체를 배열로 다루기
조상타입의 참조변수로 자손타입의 객체를 참조하는 것이 가능하므로
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();
조상타입의 참조변수 배열을 사용하면, 공통의 조상을 가진 서로 다른 종류의 객체를 배열로 묶어서 다룰 수 있다.
package object_oriented_programming2;
class Product1{
int price;
int bonusPoint;
Product1(int price){
this.price = price;
bonusPoint =(int)(price/10.0);
}
Product1() {}
}
class Tv1 extends Product1{
Tv1() {super(100); }
public String toString() {return "Tv";}
}
class Computer1 extends Product1{
Computer1() {super(200);}
public String toString() {return "Computer";}
}
class Audio extends Product1{
Audio() {super(50);}
public String toString() {return "Audio";}
}
class Buyer1{
int money = 1000;
int bounsPoint = 0;
Product1[] item = new Product1[10];
int i = 0;
void buy(Product1 p) {
if(money < p.price) {
System.out.println("잔액이 부족하여 물건을 살 수 없습니다.");
return;
}
money -= p.price;
bounsPoint += p.bonusPoint;
item[i++] = p;
System.out.println(p+"을/를 구입하였습니다.");
}
void summary() {
int sum=0;
String itemList="";
for(int i=0; i<item.length; i++) {
if(item[i]==null) break;
sum+=item[i].price;
itemList += item[i]+", ";
}
System.out.println("구입하신 물품의 총 금액은 "+sum+"만원입니다.");
System.out.println("구입하신 제품은 "+itemList+"입니다.");
}
}
public class PolyArgumentTest2 {
public static void main(String[] args) {
Buyer1 b = new Buyer1();
b.buy(new Tv1());
b.buy(new Computer1());
b.buy(new Audio());
b.summary();
}
}
//결과
Tv을/를 구입하였습니다.
Computer을/를 구입하였습니다.
Audio을/를 구입하였습니다.
구입하신 물품의 총 금액은 350만원입니다.
구입하신 제품은 Tv, Computer, Audio, 입니다.
Vector
메서드 / 생성자 | 설명 |
---|---|
Vector() | 10개의 객체를 저장할 수 있는 Vector인스턴스를 생성한다. 10개 이상의 인스턴스가 저장되면, 자동적으로 크기가 증가된다. |
boolean add(Object o) | Vector에 객체를 추가한다. 추가에 성공하면 결과값으로 true, 실패하면 false를 반환한다. |
boolean remove(Object o) | Vector에 저장되어있는 객체를 제거한다. 제거에 성공하면 true, 실패하면 false를 반환한다. |
boolean isEmpty() | Vector가 비어있는지 검사한다. 비어있으면 true, 비어있지 않으면 false를 반환한다. |
Object get(int index) | 저장된 위치(index)의 객체를 반환한다. 반환타입이 Object타입이므로 적잘한 타입으로의 형변환이 필요하다. |
int size() | Vector에 저장된 객체의 개수를 반환한다. |
package object_oriented_programming2;
import java.util.Vector;
class Product2{
int price;
int bonusPoint;
Product2(int price){
this.price = price;
bonusPoint =(int)(price/10.0);
}
Product2() {
price =0;
bonusPoint = 0;
}
}
class Tv4 extends Product2{
Tv4() {super(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 bounusPoint = 0;
Vector item = new Vector();
void buy(Product2 p) {
if(money < p.price) {
System.out.println("잔액이 부족하여 물건을 살수 없습니다.");
return;
}
money -= p.price;
bounusPoint += p.bonusPoint;
item.add(p);
System.out.println(p+"을/를 구입하셨습니다.");
}
void refund(Product2 p) {
if(item.remove(p)) {
money += p.price;
bounusPoint -= 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++) {
Product2 p = (Product2)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) {
Buyer2 b = new Buyer2();
Tv4 tv = new Tv4();
Computer2 com = new Computer2();
Audio2 audio = new Audio2();
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입니다.
출처 : JAVA의 정석 - (남궁성지음)