[Java] 다형성(polymorphisom)

재훈·2024년 3월 15일

1. 다형성이란?

객체지향에서의 다형성이란 '여러 가지 형태를 가질 수 있는 능력'을 의미한다. 구체적으로는 조상클래스 타입의 참조변수로 자손클래스의 인스턴스를 참조할 수 있는 성질이다.

class Tv {
	boolean power;		// 전원상태(on/off)
    int channel;		// 채널
    
    void power()	 {	power = !power	}
    void channelUp() {	power = !power 	}
    void channelUp() {	power = !power 	}
}
class CaptionTv extends Tv {
	String text;	// 캡션을 보여 주기 위한 문자열
    void caption() { /* 내용생략 */ }

위와 같은 클래스들이 있을 때 Tv와 CaptionTv는 상속 관계에 있다. 두 클래스의 인스턴스를 생성하기 위해서는 아래와 같은 방법을 사용할 수 있다.

Tv t = new Tv();
CaptionTv c = new CationTv();

만약 인스턴스를 같은 타입의 참조변수가 아닌 조상타입의 참조변수자손타입의 참조변수를 사용하여 참조하는 것이 가능할까?

조상타입의 참조변수로 참조하는 경우

Tv t = new CaptionTv();

CaptionTv 인스턴스를 Tv타입의 참조변수 t를 사용하여 참조할 수 있지만, Tv타입의 참조변수로는 Tv 클래스에 정의되어 있지 않은 CaptionTv 인스턴스의 멤버를 사용할 수 없다.
예를 들어 t.power()는 사용할 수 있지만, t.caption()은 사용할 수 없다.

인스턴스가 가지고 있는 멤버는 참조 변수 타입에 정의된 범위 내에서만 사용 가능하다.

자손타입의 참조변수로 참조하는 경우

CaptionTv c = new Tv();

위 코드는 컴파일하면 에러가 발생한다. 실제 인스턴스인 Tv의 멤버 개수보다 참조변수 c가 사용할 수 있는 멤버 개수가 더 많기 때문이다. 즉, 참조변수가 사용할 수 있는 멤버의 개수는 인스턴스의 멤버 개수보다 같거나 적어야한다.

조상타입의 참조변수로 자손타입의 인스턴스를 참조할 수 있다.
반대로 자손타입의 참조변수로 조상타입의 인스턴스를 참조할 수 없다.

2. 참조변수의 형변환

기본형 변수와 같이 참조형 변수도 형변환이 가능하다. 단, 서로 상속관계에 있는 클래스 사이에서만 가능하다(조상타입<-->자손타입).

기본형 변수에서 작은 자료형에서 큰 자료형의 형변환이 생략 가능한 것처럼, 자손타입의 참조변수를 조상타입으로 형변환하는 경우에는 형변환을 생략할 수 있다.

Tv t = new Tv();
Caption c = (CaptionTv)t; // (CaptionTv) 생략 불가
CaptionTv c = new CaptionTv();
Tv t = (Tv)c; // (Tv) 생략 가능

자손타입 -> 조상타입(Up-Casting) :형변환 생략가능
조상타입 -> 자손타입(Down-Casting) :형변환 생략불가

Tv t = null;
CaptionTv c = new CaptionTv();
CaptionTv c2 = null

c.caption();
t = c; // t=(Tv)c;에서 형변환 생략됨
t.caption(); // 컴파일 에러 발생! Tv타입의 참조변수로는 caption() 호출 불가

c2 = (CaptionTv)t; // Down-Cating이므로 형변환 생략불가
c2.caption();

형변환은 참조변수의 타입을 변환하는 것이지 인스턴스를 변환하는 것은 아니기 때문에 참조변수의 형변환은 인스턴스에 아무런 영향을 미치지 않는다.

위 예시에서 생성된 인스턴스는 CaptionTv 타입이므로 해당 인스턴스를 참조하는 참조변수의 타입에 따라 호출할 수 있는 멤버의 범위만 달라질 뿐이다. 따라서 같은 인스턴스라도 Tv 타입의 참조변수로는 Caption()을 호출할 수 없지만, CaptionTv 타입의 참조변수로는 Caption()을 호출할 수 있다.

3. instanceof 연산자

참조변수가 참조하고 있는 인스턴스의 실제 타입을 알아보기 위해 instanceof 연산자가 사용된다(주로 조건문). 왼쪽에는 참조변수를 오른쪽에는 타입(클래스명)이 피연산자로 위치하고, 결과로는 boolean 값이 반환된다.

class InstanceofTest {
	public static void main(String args[]) {
    	CaptionTv c = new CaptionTv();
        
        if(c instanceof CaptionTv) {
        	 System.out,println("This is a CaptionTv instance.");
        }
        if(c instanceof Tv) {
        	 System.out,println("This is a CaptionTv instance.");
        }
        if(c instanceof Object) {
        	 System.out,println("This is an CaptionTv instance.");
        }
        System.out.println(c.getClass().getName()); // 클래스 이름 출력
    }
}

This is a CaptionTv instance.
This is a Tv instance.
This is an Object instance.

어떤 타입의 대한 instanceof 연산의 결과가 true라는 것은 검사한 타입으로 형변환이 가능하다는 것을 뜻한다.

4. 참조변수와 인스턴스의 연결

자손 클래스에서 조상 클래스의 멤버변수를 중복으로 정의하면, 호출되는 인스턴스 변수는 참조변수의 타입에 따라 달라진다.

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);
        c.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

메서드와 달리 멤버변수가 중복될 경우, 인스턴스의 타입과 상관없이 참조변수의 타입에 따라 사용되는 멤버변수가 달라진다.

따라서 멤버변수는 주로 private로 접근을 제한하고, 외부에서 메서드를 통해서만 멤버변수에 접근할 수 있도록 하는 것이 바람직하다.

0개의 댓글