* 다형성
: 다형성은 상속과 깊은 관계가 있으므로 학습하기에 앞서 상속에 대해 충분히 알고 있어야 한다.
객체지향개념에서의 다형성이란 '여러 가지 형태를 가질 수 있는 능력'을 의미하며, 자바에서는 한 타입의 참조변수로 여러 타입의 객체를 참조할 수 있도록 함으로써 다형성을 프로그램적으로 구현하였다.
이를 좀 더 구체적으로 말하자면, 조상클래스 타입의 참조변수로 자손클래스의 인스턴스를 참조할 수 있도록 하였다는 것이다.
<예문>
class Tv1{ // Tv의 속성(멤버변수) String color; //색상 boolean power; //전원상태(on/off) int channel; //채널 // Tv의 기능(메서드) void power() { power = !power; } /* TV를 켜거나 끄는 기능을 하는 메서드 */ void channelUp() { ++channel; } /* TV의 채널을 높이는 기능을 하는 메서드 */ void channelDown() { --channel; } /* TV의 채널을 낮추는 기능을 하는 메서드 */ } public class CaptionTv extends Tv{ String text; //캡션을 보여 주기 위한 문자열 void caption() { System.out.println("test song"); } public static void main(String[] args) { // TODO Auto-generated method stub CaptionTv c = new CaptionTv(); Tv t = new CaptionTv(); //CationTv c1 = new Tv(); // 실제 인스턴스인 Tv의 멤버 개수보다 참조변수 c가 사용할 수 있는 // 멤버 개수가 더 많기 때문이다. c.caption(); //t.caption(); // Tv 타입의 참조변수로는 CaptionTv 인스턴스 중에서 Tv 클래스의 멤버들 // (상속받은 멤버 포함)만 사용할 수 있다. } }
- 조상타입의 참조변수로 자손타입의 인스턴스를 참조할 수 있다.
- 반대로 자손타입의 참조변수로 조상타입의 인스턴스를 참조할 수는 없다.
* 참조변수의 형변환
: 기본형 변수와 같이 참조변수도 형변환이 가능하다. 단, 서로 상속관계에 있는 클래스사이에서만 가능하기 때문에 자손타입의 참조변수를 조상타입의 참조변수로, 조상타입의 참조변수를 자손타입의 참조변수로의 형변환만 가능하다.
[참고] : 바로 윗 조상이나 자손이 아닌 간접적인 상속관계, 예를 들면 조상의 조상에 있는 경우에도 형변환이 가능하다. 따라서 모든 참조변수는 모든 클래스의 조상인 Object 클래스 타입으로 형변환이 가능하다.
- 자손타입 -> 조상타입 (Up-casting) : 형변환 생략 가능
- 자손타입 <- 조상타입 (Down-casting) : 형변환 생략 불가
<예문>
class Car1 { String color; int door; void dirve(){ //운전하는 기능 System.out.println("drive, Brrrr~"); } void stop(){ //멈추는 기능 System.out.println("stop!!"); } } class FireEngine extends Car1{ //소방차 void Water(){ //물 뿌리는 기능 System.out.println("Water!!"); } } class Ambulance extends Car1{ //앰뷸런스 void siren(){ //사이렌을 울리는 기능 System.out.println("siren~~~"); } }
위의 예문과 같이 세 클래스, Car1, FireEngine, Ambulance 가 정의되어 있을 때, 이 세 클래스 간의 관계를 표현하면 다음과 같다.
=> FireEngine -> Car1 <- Ambulance
[참고] : 이처럼 클래스 간의 상속관계를 그림으로 나타내 보면, 형변환의 가능여부를 쉽게 확인할 수 있다.
Car1 클래스는 FireEngine 클래스와 Ambulance 클래스의 조상이다. 그렇다고 해서 FireEngine 클래스와 Ambulance 클래스가 형제 관계는 아니다. 자바에서는 조상과 자식관계만 존재하기 때문에 FireEngine 클래스와 Ambulance 클래스는 서로 아무런 관계가 없다.
따라서 Car1 타입의 참조변수와 FireEngine 타입의 참조변수 그리고 Car1 타입의 참조변수와 Ambulance 타입의 참조변수 간에는 서로 형변환이 가능하지만, FireEngine 타입의 참조변수와 Ambulance 타입의 참조변수 간에는 서로 형변환이 가능하지 않다.
<예문1>
FireEngine f; Ambulance a; //a = (Ambulance)f; //컴파일 에러!! //f = (FireEngine)a; //컴파일 에러!!
<예문2>
Car1 car = null; FireEngine fe = new FireEngine(); FireEngine fe2 = null;
car = fe; // car=(Car1)fe;에서 형변환이 생략된 형태이다.
fe2 = (FireEngine)car; // 형변환을 생략할 수 없다.
Car1 타입의 참조변수 c를 Car1 타입의 조상인 Object 타입의 참조변수로 형변환 하는 것은 참조변수가 다룰 수 있는 멤버의 개수가 실제 인스턴스가 갖고 있는 멤버의 개수보다 적을 것이 분명하므로 문제가 되지 않는다. 그래서 형변환을 생략할 수 있도록 한 것이다.
<예문>
Object ob = (Object)car; // (Object)를 생략할 수 있음!
* instanceof 연산자
: 참조변수가 참조하고 있는 인스턴스의 실제 타입을 알아보기 위해 instanceof 연산자를 사용한다. 주로 조건문에 사용되며, instanceof 의 왼쪽에는 참조변수를 오른쪽에는 타입(클래스명)이 피연사자로 위치한다. 그리고 연산의 결과로 boolean값인 true, false 중의 하나를 반환한다.
instanceof를 이용한 연산결과로 true를 얻었다는 것은 참조변수가 검사한 타입으로 형변환이 가능하다는 것을 뜻한다.
<예문>
void doWork(Car1 c){ if(c instanceof FireEngine){ FireEngine fe = (FireEngine)c; fe.Water(); // ... } else if(c instanceof Ambulance){ Ambulance a = (Ambulance)c; a.siren(); // ... } // ... }
* 참조변수와 인스턴스의 연결
: 메서드의 경우 조상 클래스의 메서드를 자손의 클래스에서 오버라이딩한 경우에도 참조변수의 타입에 관계없이 항상 실제 인스턴스의 메서드(오버라이딩된 메서드)가 호출되지만, 멤버변수의 경우 참조변수의 타입에 따라 달라진다.
멤버변수가 조상 클래스와 자손 클래스에 중복으로 정의된 경우, 조상 타입의 참조변수를 사용했을 때는 조상 클래스에 선언된 멤버변수가 사용되고, 자손타입의 참조변수를 사용했을 때는 자손 클래스에 선언된 멤버변수가 사용된다.
- 참조변수를 통해 중복된 메서드를 호출할 경우 : 인스턴스의 타입을 따라간다!!
- 참조변수를 통해 중복된 멤버변수를 호출할 경우 : 참조변수의 타입을 따라간다!!
- 참조변수를 통해 중복되지 않은 메서드나 멤버변수를 호출할 경우 : 조상의 멤버들을 사용한다!!
- 참조변수의 타입에 따라 결과가 달라지는 경우는 조상 클래스의 멤버변수와 같은 이름의 멤버변수를 자손 클래스에 중복해서 정의한 경우 뿐이다.
<예문>
public class BindingTest { /** * @param args */ public static void main(String[] args) { // TODO Auto-generated method stub Parent1 p1 = new Child1(); Child1 c1 = new Child1(); Parent1 p2 = new Child2(); Child2 c2 = new Child2(); System.out.println("p1.x = "+p1.x); p1.method(); System.out.println("c1.x = "+c1.x); c1.method(); System.out.println("p2.x = "+p2.x); p2.method(); System.out.println("c2.x = "+c2.x); c2.method(); } } class Parent1{ int x = 100; void method(){ System.out.println("Parent1 Method"); } } class Child1 extends Parent1{ int x = 200; void method(){ System.out.println("Child1 Method"); } } class Child2 extends Parent1{}
* 매개변수의 다형성
: 참조변수의 다형적인 특징은 메서드의 매개변수에도 적용된다.
아래 예문과 같이 매개변수가 Product 타입의 참조변수라는 것은, 메서드의 매개변수로 Product클래스의 자손타입의 참조변수면 어느 것이나 매개변수로 받아들일 수 있다는 뜻이다.
다른 제품 클래스를 추가할 때 Product클래스를 상속받기만 하면, buy(Product p)메서드의 매개변수로 받아들여질 수 있다.
<예문>
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(Product p){ // Buyer 가 가진 돈(money)에서 제품의 가격(t.price)만큼 뺀다. money = money - p.price; // Buyer 의 보너스 점수(bonusPoint)에 제품의 보너스 점수(t.bonusPoint)를 더한다. bonusPoint = bonusPoint + p.bonusPoint; } } public class ProductTest{ public static void main(String[] args){ Buyer b = new Buyer(); Tv t = new Tv(); Computer c = new Computer(); t.price = 100; t.bonusPoint = 10; c.price = 200; c.bonusPoint = 20; b.buy(t); System.out.println("first money : ["+b.money+"] bonusPoint["+b.bonusPoint+"]"); b.buy(c); System.out.println("second money : ["+b.money+"] bonusPoint["+b.bonusPoint+"]"); } }
* 여러 종류의 객체를 하나의 배열로 다루기
: 조상타입의 참조변수로 자손타입의 객체를 참조하는 것이 가능하므로, 조상타입의 참조변수 배열을 사용하면, 공통의 조상을 가진 서로 다른 종류의 객체를 배열로 묶어서 다룰 수 있다.
또는 묶어서 다루고자하는 객체들의 상속관계를 따져서 가장 가까운 공통조상 클래스 타입의 참조변수 배열을 생성해서 객체들을 저장하면 된다.
<예문1>
Product p1 = new Tv(); Product p2 = new Computer(); Product p3 = new Audio();
<예문2>
Product p[] = new Product[3]; p[0] = new Tv(); p[1] = new Computer(); p[2] = new Audio();
위의 예문1과 같이 조상타입의 참조변수로 자손타입의 객체를 참조하는 것이 가능하고, 예문2와 같이 서로 다른 종류의 객체를 조상타입의 참조변수 배열로 객체를 저장해도 된다.
[참고] 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에 저장된 객체의 개수를 반환한다.
'개발 관련 지식 > 자바(Java)' 카테고리의 다른 글
[자바] 추상 클래스(abstract class) (0) | 2014.07.02 |
---|---|
[자바] 상속(inheritance) (0) | 2014.07.02 |
[자바] 제어자(modifier) (0) | 2014.07.02 |
[자바] 패키지(Package) 와 임폴트(Import) (0) | 2014.07.02 |
[자바] 오버라이딩(Overriding) (0) | 2014.07.02 |