Dico

[Java] 다형성

다형성 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판