[Java] 다형성

황중섭·2023년 11월 2일

자바와 같은 객체지향언어를 공부하다 보면 다형성이라는 개념을 마주할 수 밖에 없다. 개인적으로는 이 부분이 객체지향언어를 배울 때 가장 어렵지 않을까 생각하는데, 그만큼 중요한 부분이기도 하기 때문에 지금부터 다형성에 관해 작성해 보겠다.


1. 다형성이란

다형성의 한자어를 그대로 풀이하면 "다양한 형태를 가질 수 있는 성질" 정도를 의미할 것이다. 그렇다면 누가 다양한 형태를 가질 수 있다는 걸까?
자바로 어떤 프로그램을 만든다고 하면 모든 기능은 메서드를 통해 구현하고 그 메서드들은 클래스, 즉 객체 안에 있다. 그리고 프로그래머는 그러한 객체를 참조변수를 통해 이용한다. 이 참조변수가 다양한 형태를 가질 수 있는 것이다. 그러면 당연히 이 다양한 형태는 다양한 클래스 타입의 객체가 된다.

이를 조금 더 구체적으로 말하자면, 다형성은 조상 클래스 타입의 참조변수로 자손 클래스의 인스턴스(객체)를 참조할 수 있는 성질이라고 할 수 있다.
자바에서는 다형성과 아주 밀접하게 연관된 개념들이 있는데, 바로 상속추상화이다.



2. 상속

상속은 기존의 클래스를 재사용하여 새로운 클래스를 작성하는 것이다.

class Car {
    String color;
    int door;

    void drive() {
        //운전하는 기능
    }

    void stop() {
        //정지하는 기능
    }
}

위와 같은 클래스가 있다고 하자. 그런데 자동차에 호스를 달아 소방차를 만들고 싶다면, 다시 처음부터 필요한 변수와 메서드를 하나하나 작성하는 것이 아니라 다음과 같이 기존 Car 클래스에 호스와 물을 뿌리는 기능만 추가해 만들면 된다. 상속의 핵심은 코드를 재사용하는 것이다.

class FireEngine extends Car {
	int fireHose;

    void water() {
        //물을 뿌리는 기능
    }
}

이렇게 상속받은 클래스의 인스턴스를 만들어 사용하고자 하면 아마 다음과 같이 할 것이다.

FireEngine fireEngine = new FireEngine();

물론 이렇게도 할 수 있지만, 다음 방식으로도 쓸 수 있다. 이것이 다형성을 이용한 방식이다.

Car car = new FireEngine();

그러나 이렇게 조상타입의 참조변수(car)로 자손타입의 인스턴스(FireEngine 객체)를 참조할 때에는 주의해야할 부분이 있는데, 자손타입의 클래스가 가지고 있는 멤버(메서드나 변수)는 사용이 불가능하다는 것이다. 즉 drive()는 가능하지만 water()는 불가능하다.

Car car = new FireEngine();

car.drive() //O
car.water() //X

참조변수가 쓸 수 있는 멤버는, 참조하고 있는 인스턴스의 타입(FireEngine)이 아니라 그 참조변수의 타입(Car)을 따라간다.

만약에 car에서 water()와 같은 FireEngine에만 있는 멤버를 쓰고 싶으면 FireEngine으로 강제 형변환을 해주면 된다.

(FireEngine) car.water()

이런 식으로 조상타입의 참조변수를 자손타입으로 형변환하는 것을 다운캐스팅이라 하고, 반대로 자손타입의 참조변수를 조상타입으로 형변환하는 것을 업캐스팅이라 한다. 또한 다운캐스팅은 형변환을 명시해줘야하지만 업캐스팅은 이를 생략할 수 있다.

한편 다음과 같이 코드를 작성하면 아예 컴파일 시점에 에러가 발생한다.

FireEngine fireEngine = new Car();

위에서 참조변수가 쓸 수 있는 멤버는 그 참조변수의 타입을 따라간다고 했다. fireEngine도 이에 따라서 fireEngine.water()를 실행하고자 할 수도 있다. 그러나 fireEngine이 실제로 참조하고 있는 것은 Car 클래스의 인스턴스이므로 여기에는 water()라는 기능이 없다. 즉 자손타입의 참조변수로 조상타입의 인스턴스를 참조하는 것은, 존재하지 않는 멤버를 사용하고자 할 가능성이 있으므로 허용하지 않는 것이다.

그러면 업캐스팅이라는 것도 있으니 이걸 써서 위의 코드를 다음과 같이 고치면 되지 않을까?

Car car = new Car();
FireEngine fireEngine = (FireEngine) car;

이것도 안된다. 형변환은 참조변수의 타입을 변환하는 것이지, 인스턴스 자체를 변환하는 것은 아니다. car는 지금 Car의 인스턴스를 참조하고 있다. Car의 인스턴스는 water()fireHose 처럼 FireEngine에서 추가한 멤버를 가지고 있지 않다. 즉 위에서 얘기한 것과 똑같은 이유다. 존재하지 않는 멤버를 사용하고자 할 가능성이 있어서 안되는 것이다.

업캐스팅이 가능한 경우는 다음과 같다.

Car car = new FireEngine();
FireEngine fireEngine = (FireEngine) car;

정리하자면 조상타입의 참조변수를 자손타입의 참소변수로 형변환하는 것(업캐스팅)이 가능하긴 하지만, 조상타입의 참조변수가 실제로 참조하는 인스턴스도 조상타입이면 형변환이 안된다.



3. 추상화

...작성중...

참고 자료

  • Java의 정석(남궁성 저)

0개의 댓글