Java

[JAVA] 다형성이란? 다형성이 필요한 이유 | 오버라이딩, 오버로딩, 타입 변환(업캐스팅)

syj0522 2024. 3. 6. 22:26

다형성은 무엇이고, 왜 필요할까요?

 

다형성은 객체 지향적 설계를 위한 4가지 원리 중 하나입니다.
여기서 알 수 있듯이 다형성의 목적은 객체 지향의 실현입니다.

 

다형성이 어떻게 객체 지향을 실현하는지 살펴봅시다.

 

다형성이란?

다형성은 어떤 객체의 속성이나 기능이 상황에 따라 다른 역할을 수행할 수 있는 것을 의미합니다.
다형성은 오버라이딩, 오버로딩, 타입 변환 등에서 다양하게 나타날 수 있는데요, 하나씩 설명해보겠습니다.

 

(1) 오버라이딩

상위 클래스 메서드를 하위클래스에서 재정의하여 사용

public interface Vehicle { //Vehicle 인터페이스
    void start();
    void moveForward();
    void moveBackward();
}
public class Car implements Vehicle { //Vehicle 인터페이스를 구현한 Car 클래스

    @Override
    public void moveForward(){
        System.out.println("전진");
    }
    @Override
    public void moveBackward(){
        System.out.println("후진");
    }
}

Vehicle 인터페이스에 정의된 moveForward(), moveBackward() 메서드가 Car 클래스에서 오버라이딩(재정의)되었습니다.

 

필요에 따라 moveForward(), moveBackward()가 다른 하위 클래스에서 다른 역할을 하도록 또 재정의할 수 있겠죠.
이처럼 같은 이름의 메서드가 상황에 따라 다른 역할을 수행하면 다형성을 가진다고 합니다.

 

(2) 오버로딩

이름은 같지만 매개변수 타입, 개수가 다른 메서드를 여러 개 생성

class Calculator {
    void add(int a, int b) { 
        int result = a + b;
        System.out.println("a+b=" + result);
    }
    void add(int a, int b, int c) { 
        int result = a + b + c;
        System.out.println("a+b+c=" + result);
    }    
    void add(String a, String b) { 
        String result = a + b;
        System.out.println("문자열: " + result);
    }
}
public class Main {
    public static void main(String[] args){
        Calculator c = new Calculator();
        c.add(1, 2); //a+b=3
        c.add(1, 2, 3); //a+b+c=6
        c.add("abc", "def"); //문자열: abcdef
    }
}

매개변수 타입과 개수가 다른 3개의 add()메서드를 만들었습니다.
호출 시 매개변수 타입과 개수를 다르게 주면 그에 맞는 메서드가 호출됩니다.
마찬가지로 같은 이름의 메서드가 상황에 따라 다른 역할을 수행하고 있죠. 다형성이 실현되었습니다.

 

(3) 타입 변환 : 업캐스팅

클래스가 상속관계에 있을 때, 상위 클래스 타입의 참조 변수로 하위 클래스 객체를 참조하기

public interface Vehicle { //Vehicle 인터페이스
    void start();
    void moveForward();
    void moveBackward();
}
public class Car implements Vehicle { //Vehicle 인터페이스를 구현한 Car 클래스

    @Override
    public void moveForward(){
        System.out.println("전진");
    }
    @Override
    public void moveBackward(){
        System.out.println("후진");
    }
}
public class MotorBike implements Vehicle { //Vehicle 인터페이스를 구현한 MotorBike 클래스

    @Override
    public void moveForward(){
        System.out.println("전진");
    }
    @Override
    public void moveBackward(){
        System.out.println("후진");
    }
}
public class Driver { //Car, MotorBike 클래스에 의존하는 Driver 클래스
    void drive(Car car) {
        car.moveForward();
        car.moveBackward();
    }
    void drive(MotorBike motorBike) {
        motorBike.moveForward();
        motorBike.moveBackward();
    }
}
public class Main { 
    public static void main(String[] args) {
        Car car = new Car();
        MotoBike motorbike = new MotoBike();
        Driver driver = new Driver();

        driver.drive(car); 
        driver.drive(motorbike);
    }
}

Vehicle 인터페이스는 Car, MotorBike 클래스의 상위 클래스입니다.
(참고로 extends로는 하나의 클래스만 상속이 되는데, implements는 다중 상속이 가능해요. 즉 인터페이스를 구현하는 것도 상속에 해당됩니다. 단, 인터페이스를 상속받은 클래스는 인터페이스의 모든 메서드를 구현해주어야 한다는 특징이 있습니다.)

 

그리고 Driver 클래스는 Car, MotorBike의 객체를 받아야만 실행돼요. Driver과 Car, MotorBike는 의존관계로 묶여있습니다.

 

이렇게 객체 간 결합도가 높으면, 객체 지향적 설계에 불리합니다. 만약 Driver가 의존하는 클래스가 Car, MotorBike 두 개가 아니라 100개로 늘어난다면? Driver 클래스 내에 100개에 해당하는 함수를 일일이 정의해야 합니다. 또는 Driver가 의존하는 클래스가 Car에서 Bus로 바뀐다면? Driver 클래스 내 코드를 수정하는 것이 불가피해집니다.

 

객체 간 결합도를 낮추기 위해 타입 변환:업캐스팅을 사용하여 다형성을 실현해봅시다.
타입 변환에는 두 가지가 있는데, 업캐스팅(하위 클래스 객체가 상위 클래스 타입으로 형변환됨)과 다운캐스팅(상위 클래스 객체가 하위 클래스 타입으로 형변환됨)이 있습니다.

 


업캐스팅

잠시 업캐스팅에 대해 설명하고 넘어가겠습니다.

SuperObject a = new SubObject();
  • 코드 설명
    • SubObject 인스턴스 생성 후, 해당 인스턴스 주소값을 SuperObject타입의 참조변수 a에 대입
    • (두 클래스가 상속관계에 있으므로 자동 타입 변환됨)
  • 메모리의 참조 형태
    • a는 SuperObject타입으로 선언된 참조변수이지만, SubObject클래스의 생성자를 호출하여 객체를 만들었으므로 SubObject를 가리키고 있다.
    • SubObject는 자신의 상위클래스인 SuperObject를 가리키고 있다.
  • 특징
    • a는 상위 클래스의 필드, 메서드만 접근 가능하다.
    • 예외: 하위 클래스에서 메서드가 오버라이딩(재정의)되었다면 오버라이딩된 메서드가 대신 호출된다.

 

업캐스팅을 사용하면 상위 클래스 타입의 참조 변수를 통해 여러 가지 하위 클래스 타입의 객체를 참조할 수 있습니다.
상위 클래스가 하위 클래스의 동작 방식을 알 수 없어도 오버라이딩을 통해 자식 클래스에 접근할 수 있어요.

 

왜 굳이 상위 클래스 참조 변수를 사용해서 하위 클래스 객체를 가리킬까요?
상위 클래스 필드와 메서드, 그리고 하위 클래스에서 오버라이딩된 메서드만 접근 가능하고 하위 클래스 고유의 메서드는 쓸 수도 없게 되는데.
그냥 하위 클래스 객체를 선언해서 그걸 쓰면 되는 거 아닌가요?

 

업캐스팅을 사용하면 코드의 재사용성을 높이고 코드를 최대한 간결하게 표현할 수 있습니다.


상위 클래스에 정의된 필드와 메서드를 하위 클래스에 오버라이딩 했을 때를 생각해보면 바로 이해가 될 거예요.

원래는 하위 클래스의 필드와 메서드를 사용하려면 하위 클래스 객체를 일일이 생성해야 했습니다.

 

하지만 업캐스팅된 참조변수를 사용하면 일일이 하위 클래스 객체를 선언하지 않아도 업캐스팅된 참조변수 하나로 여러 개의 하위클래스의 오버라이딩된 함수에 접근할 수 있습니다.

 

마치 모든 기숙사의 에어컨을 제어하기 위해 행정실의 리모컨 하나를 사용하면 되는 것과 같습니다.

 

단, 클래스끼리 상속되어있어야 하고 상위클래스의 메서드를 오버라이딩했다는 조건 하에서 업캐스팅된 참조변수(행정실 리모컨)가 하위클래스의 오버라이딩된 메서드(기숙사 방의 에어컨 단, 행정실 에어컨과 같은 모델)에 접근할 수 있는 거겠죠.


 

다시 예제로 돌아오면, 객체 간 결합도를 낮추어 객체 지향을 실현하기 위해 타입 변환:업캐스팅을 사용한다고 했는데
변경된 코드는 아래와 같습니다.

public class Driver { 
    void drive(Vehicle vehicle) {
        vehicle.moveForward();
        vehicle.moveBackward();
    }

 

Driver 클래스가 Vehicle 인터페이스 타입 객체를 받도록 변경하고

public class Main { 
    public static void main(String[] args) {
        //Car car = new Car();
        //MotoBike motorbike = new MotoBike();
        Vehicle car = new Car();
        Vehicle motorbike = new MotoBike();
        Driver driver = new Driver();

        driver.drive(car); 
        driver.drive(motorbike);
    }
}

 

Car, MotorBike 타입의 car, motorbike 참조변수를 Vehicle타입으로 변경(타입 변환:업캐스팅)했습니다.

 

이제 car, motorbike 참조변수는 Vehicle의 moveForward(), moveBackward()에만 접근 가능하게 되었지만, Vehicle의 하위 클래스인 Car, MotorBike에서 각각의 메서드를 오버라이딩(재정의)했기 때문에 오버라이딩된 메서드가 호출될 것입니다.

 

이렇게 하면 Driver클래스가 사용하는 객체가 변경되더라도 Driver 클래스 내의 코드를 수정할 필요가 없습니다.
그리고 Bus, Train, Bicyle... 등 다른 객체가 추가되더라도 Vehicle 인터페이스에 상속되어있다면 업캐스팅으로 간단히 driver()메서드를 사용할 수 있겠네요.

 

코드의 유지 보수가 쉬워지고 보기에도 훨씬 간결해졌습니다.

다형성의 특징

  • 상속관계에서 나타난다.
  • 하위클래스에서 오버라이딩(함수 재정의)이 있어야 한다.
  • 하위 클래스 객체가 상위 클래스 타입으로 타입 변환(업캐스팅)되어야 한다.

마치며 : 요약 정리

다형성이 뭐죠? 하고 면접관이 묻는다면, 뭐라고 답해야 할까요?
다형성이란 한 마디로 같은 명령어가 서로 다른 동작을 수행하게 하는 것 정도로 묶어 이해할 수 있을 것 같아요.
그리고 객체 지향적 설계를 위해 필요한 개념이라는 설명을 더해줘야 되겠죠.

 

다형성은 같은 코드가 서로 다른 동작을 수행하도록 하는 것으로, 객체 지향적 설계에서 필수적인 역할을 합니다. 다형성은 상속관계에서 나타나는데, 하위 클래스에서 메서드가 오버라이딩되거나 하위 클래스 객체가 상위 클래스 타입으로 업캐스팅될 때가 해당됩니다.

 

 

 

* * *

제 글을 읽어주셔서 감사합니다.
잘못된 부분이 있으면 댓글로 알려주세요 :)

 

 

참고 사이트
출처 1
출처 2
출처 3
출처 4
출처 5
출처 6