Programming/Java

다형성 (Polymorphism)

Data Engineer 2015. 7. 12. 14:27

다형성이란?

다형성은 상속과 함께 객체지향 언어의 중요한 특징중 하나이다. 다형성은 상속과 관계가 있기 때문에 앞선 포스팅에 있는 상속을 미리 읽어 보는 것을 추천한다.

객체지향에서 다형성의 의미는 '다양한 형태의 객체를 참조할 수 있는 것'을 의미한다. 자바에서는 한 타입의 참조 변수를 이용하여 여러 가지 타입의 객체를 참조할 수 있도록 기능을 제공하여 다형성을 제공하고 있다.
결론부터 말하면, 부모 클래스 타입의 참조 변수를 이용하여 자식 클래스의 인스턴스참조할 수 있다는 것이다.

class Tv {
    boolean power;
    int channel;
    
    void power();
    void channelUp();
    void channelDown();
}

class CaptionTv extend Tv {
    String text;
    
    void caption() { }
}

위와 같이 클래스가 정의되어 있을 때, 아래와 같이 부모 클래스 타입의 참조 변수로 자식 클래스의 인스턴스를 참조가 가능하게 되는 것이다.

Tv t = new CaptionTv();

이렇게 부모 클래스 타입의 참조 변수가 자식 클래스 타입의 참조가 가능해지지만, CaptionTv c = new CaptionTv()로 선언하는 것과는 차이가 있다. 어떠한 차이가 있냐면, 위의 부모 클래스 타입의 참조 변수로 선언한 t로는 CaptionTv 클래스에 있는 멤버 변수 text와 method caption()을 사용할 수 없다. 다시 말해서, 둘 다 같은 타입의 인스턴스지만 참조 변수의 타입에 따라 사용할 수 있는 멤버 변수의 개수가 달라지게 된다.


형변환

서로 상속 관계에 있는 클래스를 선언한 참조 변수는 서로 형변환이 가능하다. 다시 말해서, 기본형 자료(Primitive data types)뿐만 아니라 자식 타입의 참조변수를 부모 타입의 참조변수로, 혹은 부모타입의 참조변수를 자식타입의 참조변수로 형변환이 가능하다. 그러나 기본 자료형에서도 범위가 작은 자료에서 큰 자료형으로 변환하는 경우에는 값 손실이 발생하지 않기 때문에 변환에 문제가 없으므로 형변환을 생략할 수 있다. 아래의 코드와 명시적으로 형변환을 해줄 필요가 없다.

int a = 10;
long b = a;	// 형변환 생략 가능

마찬가지로 자식 타입에서 부모 타입(Up-casting)을 하는 경우에는 형변환이 생략이 가능하지만, 반대의 경우인 부모타입에서 자식타입(Down-casting)으로는 형변환 생략이 불가능하다. 이 경우에는 반드시 명시적으로 형변환을 해주어야 한다.


참조변수와 인스턴스의 관계

부모 타입의 참조변수와 자식 타입의 참조 변수의 가장 큰 차이점은 멤버의 개수가 다른 것이다. 만약 부모 클래스에 선언된 멤버변수와 같은 이름의 인스턴스 변수를 자식 클래스에 중복으로 정의했을 때 차이를 보인다. 다시 말해서, 멤버변수가 부모 클래스와 자식 클래스에 중복으로 정의된 경우, 부모 타입의 참조변수를 사용했을 때는 부모 클래스에 선언된 멤버변수가 사용되고, 자식 타입의 참조변수를 사용했을 때 자식 클래스에 선언된 멤버변수가 사용된다. 글로만 보면 이해가 어려울 수도 있으니 아래의 예제 코드를 보면 이해가 좀 더 잘 될 것이다.

class BindingTest {
    public static void main(String[] args) {
        Parent p = new Child();
        Child c = new Child();
        
        System.out.println("p.x = " + p.x);
        p.method();

        System.out.println("c.x = " + c.x);
        p.method();
    }

    class Parent {
        int x = 100;
    
        void method() {
            System.out.println("Parent method...");
        }
    }

    class Child extends Parent {
        int x = 200;
    
        void method() {
            System.out.println("Child method...");
        }
    }
}

실행 결과

p.x = 100
Child method...
c.x = 200
Child method...

실행 결과를 보면 메서드인 method()의 경우 참조변수의 타입에 관계없이 항상 실제 인스턴스의 타입인 Child 클래스에 정의된 메서드가 호출되지만, 인스턴스 변수인 x는 참조 변수의 타입에 따라 달라지게 된다.




Reference

[1] 자바의 정석