[JAVA] 객체지향 프로그래밍(OOP) - 다형성과 형변환

2022. 9. 8. 15:19

다형성(Polymorphism)

다형성이란 상속 관계에 있는 객체들 중 상위 클래스의 참조변수가 하위 클래스의 인스턴스를 참조할 수 있는 성질이다.

class Parent {
    void print(){
        System.out.println("Parent");
    }
}
class Child extends Parent {
    void print() {
        System.out.println("Child");
    }

    void childDoStudy() {
        System.out.println("Child do study");
    }
}

public class PolymorphismTest {
    public static void main(String[] args) {
        Parent p1 = new Parent();
        Child c1 = new Child();

        p1.print(); //Parent
        c1.print(); //Child

        Parent p2 = new Child();
        p2.print(); //Child
    }
}

p1과 c1은 각각 참조변수 타입과 같은 타입의 인스턴스를 참조하고 있다.

결과는 당연히 각각의 클래스에 정의한 print문이 실행된다. 하지만 p2로 메서드를 콜 하면 Child 클래스의 print 메서드가 실행된다. 그 이유는 p2가 Child 타입의 인스턴스를 참조하고 있기 때문이다.

 

그러면 반대로 Child 타입의 참조변수가 Parent 클래스의 인스턴스를 참조할 수 있을까?

일반적으로 상위 클래스로부터 상속받은 하위 클래스의 멤버의 개수가 상위 클래스의 멤버의 개수보다 많기 때문에 하위 클래스는 상위 클래스를 참조할 수 없다. 

 

즉, 다형성에서 중요한 점은 상위 클래스만 하위 클래스를 참조할 수 있고 참조변수가 참조하고 있는 인스턴스의 타입에 따라 사용할 수 있는 멤버의 개수를 결정한다는 점이다.


참조변수의 타입 변환

참조변수도 기본형(int, double...)과 같이 형변환이 가능하다. 차이점은 기본형은 double 3.6을 int로 형변환하면 값이 바뀌지만 참조변수의 형변환은 사용할 수 있는 멤버의 개수만 달라지는 것뿐이다. 이때 하위 클래스가 상위 클래스로의 타입 변환을 업캐스팅이라 하고 상위 클래스에서 하위 클래스로의 타입 변환을 다운캐스팅이라고 한다.

 

타입 변환의 조건
1. 상속 관계의 클래스
2. 업캐스팅은 연산자 생략 가능 / 다운 캐스팅은 연산자 생략 불가능
3. 형제 클래스는 타입 변환 불가능

형변환을 코드와 그림을 같이 보자.

class Parent {
    void print(){
        System.out.println("Parent");
    }
}
class Child extends Parent {
    void print() {
        System.out.println("Child");
    }

    void childDoStudy() {
        System.out.println("Child do study");
    }
}

public class PolymorphismTest {
    public static void main(String[] args) {
        Parent p = null;
        Child c1 = new Child();
        Child c2 = null;

        p = c1; //업캐스팅 -> (Parent) 생략 가능
//        p.childDoStudy(); //에러!! Parent 타입의 참조변수는 Child 클래스의 메서드를 호출할 수 없다.
        c1.childDoStudy(); //Child do study
        p.print(); //Child

        c2 = (Child) p; //다운캐스팅 -> 생략 불가능
        c2.print(); //Child
        c2.childDoStudy(); //Child do study
    }
}

 

1) Parent p = null;

Parent 타입의 참조변수 p를 null로 초기화

 

2) Child c1 = new Child();

c1을 Child 타입으로 생성한 인스턴스를 참조

 

3) Child c2 = null;

Child 타입의 참조변수 c2를 null로 초기화

 

4) p = c1 // (Parent)생략

참조변수 p는 c1이 가리키고 있는 Child 타입의 인스턴스 참조 

p는 Parent 타입의 참조변수라서 childDoStudy() 호출 불가능

 

5) c2 = (Child) p

p가 참조하고 있는 인스턴스 Child를 c2가 참조.

p와 c2의 참조변수 타입이 다르고 상위 타입에서 하위 타입으로 형변환 되기때문에 형변환 생략 불가능

 

위 과정을 통해 형변환은 참조변수의 타입을 변환하는 것 뿐이고 인스턴스는 건드리지 않기 때문에 참조변수 간의 형변환은 인스턴스에는 영향을 끼치지 않고 사용할 수 있는 멤버의 개수만 조절되는 것뿐이라는 것을 알 수 있다.

 

형변환 시 주의점

public class PolymorphismTest {
    public static void main(String[] args) {
        Parent p = new Parent();
        p.print();

        Child c = (Child) p;
        c.childDoStudy();
    }
}

위의 코드는 제대로 실행되지 않고 런타임 에러가 발생한다. 

그 이유는 참조변수 p의 참조 인스턴스가 Parent 타입이기 때문이다.

 

앞에서 하위 클래스는 상위 클래스의 인스턴스를 참조할 수 없다고 했다.

p의 참조변수를 Child 타입으로 형변환 해서 문제가 없을 것 같지만 실제 인스턴스는 Parent 타입이기 때문에 형변환 과정에서 런타임 에러가 발생한다.

결국 상속관계에 있는 클래스 간 형변환은 자유롭게 이루어질 수 있지만 참조변수가 가리키는 인스턴스의 타입이 무엇인지가 가장 중요하다. 


instanceof 연산자

자바는 형변환이 가능한지 여부를 알려주는 instanceof 연산자를 제공해준다.

형변환 하기 전에 형변환이 가능한지 확인하고 형변환 하도록 하자.

 

public class PolymorphismTest {
    public static void main(String[] args) {
        Parent p1 = new Parent();

        System.out.println(p1 instanceof Object); //true
        System.out.println(p1 instanceof Parent); //true
        System.out.println(p1 instanceof Child); //false

        Parent p2 = new Child();
        
        System.out.println(p2 instanceof Object); //true
        System.out.println(p2 instanceof Parent); //true
        System.out.println(p2 instanceof Child); //true
    }
}

 

BELATED ARTICLES

more