다형성 Polymorphism
여러 가지 형태를 가질 수 있는 능력.
조상클래스 참조변수명 = new 자손클래스();
- 조상 클래스 타입의 참조변수로 자손 클래스의 인스턴스를 참조할 수 있다.
참조변수의 타입이 참조변수가 참조하고 있는 인스턴스에서 사용할 수 있는 멤버의 개수를 결정한다.
- 조상 인스턴스의 멤버 개수 <= 자손 인스턴스의 멤버 개수
클래스는 상속을 통해서 확장될 수는 있어도 축소될 수는 없다.
따라서 자손 타입의 참조변수로 조상 타입의 인스턴스를 참조할 수 없다.
public class Test {
public static void main(String[] args) {
Parent p = new Child();
}
}
class Parent {
protected int iv;
public void parentMethod() {
}
}
class Child extends Parent {
public int cIv;
}
Parent 멤버 | Child 멤버 (상속 포함) |
---|---|
# iv : int | # iv : int |
+ parentMethod : void | + parentMethod : void |
+ cIv : int |
참조변수 p는 Child의 멤버 중에서 Parent에게 상속받은 멤버에만 접근하여 사용할 수 있다.
참조변수의 형변환
캐스트연산자를 사용하여 변환한다.
참조하고 있는 인스턴스에서 사용할 수 있는 멤버의 범위(개수)를 조절한다.
(참조변수의 타입을 변환하는 것일 뿐 인스턴스에 아무런 영향을 미치지 않는다.)
- 서로 상속관계에 있는 클래스 사이에서만 가능하다.
public class Test {
public static void main(String[] args) {
Child c1 = null;
Child c2 = null;
c1 = (Child) c2; // error
c2 = (Child) c1; // error
}
}
class Parent { void parentMethod(){} }
class Child extends Parent { int cIv; }
- 업 캐스팅 Up-casting : 자손타입의 참조변수를 조상타입의 참조변수로 변환. 형변환 생략 가능.
Child c = new Child();
Parent p = c;
p.cIv = 2; // error
- 다운 캐스팅 Down-casting : 조상타입의 참조변수를 자손타입의 참조변수로 변환. 형변환 생략 불가능.
Parent p = new Child();
Child c = (Child) p;
c.parentMethod();
- 참조변수가 가리키는 인스턴스의 타입
서로 상속관계에 있는 클래스 타입의 참조변수간의 형변환은 자유로우나
참조변수가 참조하고 있는 인스턴스의 자손타입으로 형변환하는 것은 허용하지 않는다.
참조변수가 가리키는 인스턴스의 타입이 무엇인지 확인하는 것이 중요하다.
Parent p = new Parent();
Child c = (Child) p; // error
instanceof 연산자
참조변수가 참조하고 있는 인스턴스의 실제 타입을 확인한다.
연산 결과는 boolean 값으로 나오며, true면 참조변수가 검사한 타입으로 형변환이 가능하다는 것을 뜻한다.
Child c = new Child();
if(c instanceof Child) {} // true
if(c instanceof Parent) {} // true
if(c instanceof Object) {} // true
- 매개변수로 조상 클래스를 사용할 경우
메서드 내에서는 넘겨받은 값이 조상 클래스의 인스턴스인지 그 자손 클래스의 인스턴스인지 정확히 알 수 없으므로
instanceof 연산자를 이용해 인스턴스의 타입을 체크하고 적절히 형변환한 후 작업을 해야한다.
void method(Parent p) {
if(p instanceof Child){
Child c = (Child) p;
}
}
값이 null인 참조변수에 대해 instanceof 연산을 수행하면 false가 나온다.
참조변수와 인스턴스의 연결
메서드 : 참조변수의 타입에 관계없이 항상 실제 인스턴스의 메서드(오버라이딩된 메서드)가 호출된다.
멤버변수 : 조상클래스의 멤버변수와 같은 이름의 멤버변수를 자손 클래스에서 중복 정의할 경우, 참조변수의 타입에 따라 달라진다.
public class Test {
public static void main(String[] args) {
Parent p = new Child1();
Child1 c1 = new Child1();
Child2 c2 = new Child2();
System.out.println(p.iv + ", " + c1.iv + ", " + c2.iv); // 2, 4, 2
p.method(); // Child Method
c1.method(); // Child Method
c2.method(); // Parent Method
}
}
class Parent {
int iv = 2;
void method() {
System.out.println("Parent Method");
}
}
class Child1 extends Parent {
int iv = 4;
void method() {
System.out.println("Child Method");
}
}
class Child2 extends Parent {
}
인스턴스 변수에 직접 접근하면 참조변수의 타입에 따라 사용되는 인스턴스 변수가 달라질 수 있으므로
멤버변수들은 private으로 접근을 제한하고, 외부에서는 메서드를 통해서만 멤버변수에 접근할 수 있게 한다.
매개변수의 다형성
클래스의 조상을 매개변수로 받는다.
public class Test {
public static void main(String[] args) {
Consumer c = new Consumer();
c.buy(new Apple());
c.buy(new Orange());
System.out.println("잔돈 " + c.money + "원"); // 잔돈 7000원
}
}
class Fruit {
int price;
Fruit(int price) {
this.price = price;
}
}
class Apple extends Fruit {
Apple() {
super(1000);
}
}
class Orange extends Fruit {
Orange() {
super(2000);
}
}
class Consumer {
int money = 10000;
void buy(Fruit f) {
if (money < f.price)
return;
money -= f.price;
}
}
여러 종류의 객체를 배열로 다루기
조상타입의 참조변수 배열을 사용하면, 공통의 조상을 가진 서로 다른 종류의 객체를 배열로 묶어서 다룰 수 있다.
Fruit[] f = new Fruit[2];
f[0] = new Apple();
f[1] = new Orange();
public class Test {
public static void main(String[] args) {
Consumer c = new Consumer();
c.buy(new Apple());
c.buy(new Orange());
System.out.println("잔돈 " + c.money + "원"); // 잔돈 7000원
String buyItems = "";
for (int i = 0; i < c.list.length; i++) {
buyItems += c.list[i] + ", ";
}
System.out.println(buyItems); // Apple, Orange, null,
}
}
class Fruit {
int price;
Fruit(int price) {
this.price = price;
}
}
class Apple extends Fruit {
Apple() {
super(1000);
}
public String toString() {
return "Apple";
}
}
class Orange extends Fruit {
Orange() {
super(2000);
}
public String toString() {
return "Orange";
}
}
class Consumer {
int money = 10000;
Fruit[] list = new Fruit[3];
int i = 0;
void buy(Fruit f) {
if (money < f.price)
return;
money -= f.price;
list[i++] = f;
}
}
문자열과 참조변수의 덧셈은 참조변수에 toString()을 호출해서 문자열을 얻어 결합한다.
Class Vector
내부적으로 Object타입의 배열을 가지고 있어서 동적으로 크기가 관리되는 객체 배열
public class Vector
extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, Serializable {
Object[] elementData;
}
import java.util.Vector;
public class Test {
public static void main(String[] args) {
Consumer c = new Consumer();
c.buy(new Apple());
c.buy(new Orange());
String buyItems = "";
for (int i = 0; i < c.list.size(); i++) {
Fruit f = (Fruit) c.list.get(i);
buyItems += (i == 0) ? "" + f : ", " + f;
}
System.out.println(buyItems); // Apple, Orange
}
}
...
class Consumer {
int money = 10000;
Vector list = new Vector();
int i = 0;
void buy(Fruit f) {
if (money < f.price)
return;
money -= f.price;
list.add(f);
}
}
참고 서적: 자바의 정석 3판