다형성 Polymorphism

여러 가지 형태를 가질 수 있는 능력.
조상 클래스 타입의 참조변수로 자손 클래스의 인스턴스를 참조할 수 있다.
참조변수의 타입이 참조변수가 참조하고 있는 인스턴스에서 사용할 수 있는 멤버의 개수를 결정한다.

•  조상 인스턴스의 멤버 개수 <= 자손 인스턴스의 멤버 개수
    클래스는 상속을 통해서 확장될 수는 있어도 축소될 수는 없다.
    따라서 자손 타입의 참조변수로 조상 타입의 인스턴스를 참조할 수 없다.



참조변수의 형변환

캐스트연산자를 사용하여 변환한다.
참조하고 있는 인스턴스에서 사용할 수 있는 멤버의 범위(개수)를 조절한다.
(참조변수의 타입을 변환하는 것일 뿐 인스턴스에 아무런 영향을 미치지 않는다.)

•  서로 상속관계에 있는 클래스 사이에서만 가능하다.
public class Parent { void parentMethod(){} }
public class Child extends Parent { int cIv; }
Child c1 = null;
Child c2 = null;

c1 = (Child) c2;     // error
c2 = (Child) c1;     // error

•  다운 캐스팅 Down-casting :  조상타입의 참조변수를 자손타입의 참조변수로 변환. 형변환 생략 불가능.
•  업 캐스팅 Up-casting :  자손타입의 참조변수를 조상타입의 참조변수로 변환. 형변환 생략 가능.
Parent p = null;
Child c1 = new Child();
Child c2 = null;

p = c1;              // Up-casting
p.cIv = 2;	     // error

c2 = (Child) p;      // Down-casting
c2.parentMethod();

•  참조변수가 가리키는 인스턴스의 타입
    서로 상속관계에 있는 클래스 타입의 참조변수간의 형변환은 자유로우나
    참조변수가 참조하고 있는 인스턴스의 자손타입으로 형변환하는 것은 허용하지 않는다.
    참조변수가 가리키는 인스턴스의 타입이 무엇인지 확인하는 것이 중요하다.
Parent p = new Parent();
Child c = null;

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 Parent {
    int iv = 2; 
    void method() { Log.d("TAG", "parentMethod"); }
}

public class Child1 extends Parent {
    int iv = 4; 
    void method() { Log.d("TAG", "childMethod"); }
}

public class Child2 extends Parent { }

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        ...

        Parent p = new Child1();
        Child1 c1 = new Child1();
        Child2 c2 = new Child2();
        
	Log.d("TAG", p.iv + ", " + c1.iv + ", " + c2.iv);	 // 2, 4, 2
	p.method();						 // childMethod
	c1.method();						 // childMethod
	c2.method();						 // parentMethod
    }
}

인스턴스 변수에 직접 접근하면 참조변수의 타입에 따라 사용되는 인스턴스 변수가 달라질 수 있으므로
멤버변수들은 private으로 접근을 제한하고, 외부에서는 메서드를 통해서만 멤버변수에 접근할 수 있게 한다.



매개변수의 다형성

클래스의 조상을 매개변수로 받는다.
public class Fruit {
    int price;
    Fruit(int price) { this.price = price; }
}

public class Apple extends Fruit {
    Apple() { super(1000); }
}

public class Orange extends Fruit {
    Orange() { super(2000); }
}

public class Consumer {
    int money = 10000;
    
    void buy(Fruit f) {
         if(money < f.price) return;
	 money -= f.price;
    }
}

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        ...

        Consumer c = new Consumer();
	c.buy(new Apple());
	c.buy(new Orange());

	Log.d("TAG", "잔돈 " + c.money + "원");	 // 잔돈 7000원
    }
}



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

조상타입의 참조변수 배열을 사용하면, 공통의 조상을 가진 서로 다른 종류의 객체를 배열로 묶어서 다룰 수 있다.
Fruit[] f = new Fruit[2];
f[0] = new Apple();
f[1] = new Orange();

public class Fruit {
    int price;
    Fruit(int price) { this.price = price; }
}

public class Apple extends Fruit {
    Apple() { super(1000); }
    public String toString() { return "Apple"; }
}

public class Orange extends Fruit {
    Orange() { super(2000); }
    public String toString() { return "Orange"; }
}

public 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;
    }
}

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        ...

        Consumer c = new Consumer();
	c.buy(new Apple());
	c.buy(new Orange());

	Log.d("TAG", "잔돈 " + c.money + "원");	 // 잔돈 7000원

        String buyItems = "";
	for(int i=0; i<c.list.length; i++){
	   buyItems += c.list[i] + ", ";
	}

	Log.d("TAG", buyItems);	                 // Apple, Orange, null,
    }
}

문자열과 참조변수의 덧셈은 참조변수에 toString()을 호출해서 문자열을 얻어 결합한다.




Vector 클래스

내부적으로 Object타입의 배열을 가지고 있어서 동적으로 크기가 관리되는 객체 배열
public class Vector 
extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, Serializable {
    Object[] elementData;
}

public 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);
    }
}

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        ...

        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;
	}

	Log.d("TAG", buyItems);	                 // Apple, Orange
    }
}




참고 서적: 자바의 정석 3판