본문 바로가기

JAVA

업캐스팅(Upcasting) (feat. 다운캐스팅)

https://golden-retriever.tistory.com/16

 

오버로딩(Overloading) vs 오버라이딩(Overriding)

오버로딩(Overloading) 오버라이딩(Overriding) 목적 한 클래스 내에서 같은 이름의 메서드를 여러 개 정의하여, 매개변수의 타입이나 개수에 따라 다른 동작을 수행하도록 함 상속 관계에서 자식 클래

golden-retriever.tistory.com

 

위 글에서 동적바인딩의 예시를 보기위해 업캐스팅 얘기를 했었다.

업캐스팅은 컴파일 시점에서 추후 호출할 것처럼 보는 메서드와 실제 런타임 시점에서 호출되는 메서드가 다른, 즉 런타임 시점에서 메서드 호출이 결정되는 동적 바인딩의 예시로 쓰였다.

이 경우만 보면 굳이 혼란만 야기하는 업캐스팅을 왜 쓰나 라는 생각을 할 수 있지만 지금부터는 왜 업캐스팅이 필요한지 알아보자.

 

public class Main {
    public static void main(String[] args) {
        Chicken chicken = new Chicken();
        Cow cow = new Cow();
        
        Farm farm = new Farm();
        farm.sound(chicken);
        farm.sound(cow);
    }
}

class Farm {

    public void sound(Animal animal) {   // 업캐스팅, 이 메서드 하나로 해결
        animal.cry();
    }

    public void sound(Cow cow) {         // 이렇게 일일이
        cow.cry();
    }

    public void sound(Chicken chicken) { // 작성하지 않아도 됨.
        chicken.cry();
    }
}

class Animal {
    // 추상화 : 공통적인 특성 뽑아냈다 = 구체성(개성)이 사라진 것!
    public void cry() {
        System.out.println("소리 없음");
    }
}

class Chicken extends Animal{
    @Override
    public void cry() {
        System.out.println("꼬꼬댁");
    }

    public void fly() {
        System.out.println("파닥파닥");
    }
}

class Cow extends Animal{
    @Override
    public void cry() {
        System.out.println("음메");
    }

    public void eat() {
        System.out.println("우물우물");
    }
}

 

위 코드는 업캐스팅을 사용할 때의 장점을 잘 보여준다. Animal 클래스를 상속받는 Chicken과 Cow 클래스가 있고, Farm 클래스는 Animal 타입의 객체를 매개변수로 받는 sound 메소드를 통해 다양한 동물의 소리를 처리한다.

Farm 클래스의 sound 메서드는 모든 동물 타입에 대해 하나의 구현만을 요구한다. Animal의 서브클래스 객체를 전달하면, 해당 객체의 cry 메소드가 호출된다. 이는 비슷한 메서드(sound(Cow cow), sound(Chicken chicken))를 각각의 동물 타입마다 작성할 필요가 없게 만든다.

 

 

 또 다른 경우를 살펴보자.

class Animal {
    public void sound() {
        System.out.println("Some sound");
    }
}

class Dog extends Animal {
    @Override
    public void sound() {
        System.out.println("Bark");
    }
}

class Cat extends Animal {
    @Override
    public void sound() {
        System.out.println("Meow");
    }
}

public class TestPolymorphism {
    public static void main(String[] args) {
        Animal myDog = new Dog(); // 업캐스팅
        myDog.sound(); // "Bark"

        Animal myCat = new Cat(); // 업캐스팅
        myCat.sound(); // "Meow"
        
        Animal[] animals = new Animal[]{myDog, myCat};
        for (Animal animal : animals) {
            animal.sound(); // "Bark" 후 "Meow"
        }
    }
}

 

위 코드에서 Animal 클래스는 Dog와 Cat 클래스에 의해 상속된다. Dog와 Cat 클래스는 Animal 클래스의 sound 메서드를 오버라이딩하여 각각 고유한 소리를 내도록 한다. 메인 메서드에서는 Animal 타입의 변수를 사용하여 Dog와 Cat 객체를 참조하는 업캐스팅이 이루어진다. 배열을 사용하여 다형성을 보여주는 부분에서 여러 타입의 객체를 Animal 배열에 저장하고, 각각의 실제 타입에 따른 sound 메서드를 호출한다. 하나의 Animal 배열을 사용하여 다양한 동물(Dog, Cat 등)의 객체를 저장할 수 있다. 이는 코드를 더 간결하게 만들며, 각 동물 타입을 별도로 처리할 필요가 없게 한다.

 

이렇게 업캐스팅의 필요성에 대해 알아봤다. 그렇다면 업캐스팅을 다시 되돌리는 다운캐스팅은 어떻게 할까?

아래와 같이 2가지 방법으로 진행해주면 된다.

public class Main {
    public static void main(String[] args) {
        Animal cat = new Cat(); // 업캐스팅 : 자식 클래스로 만든 객체를 부모 클래스 타입에 담는 것
        cat.cry();

        // 다운캐스팅 : "부모 클래스 타입에 담긴" 자식 객체를 다시 자식 클래스 타입으로 바꾸는 것
        ((Cat) cat).punch();     // 첫번째 방법

        Cat realCat = (Cat) cat; // 두번째 방법
        realCat.punch();
    }
}

class Animal {
    void cry() {
        System.out.println("울음소리");
    }
}

class Cat extends Animal {
    void cry() {
        System.out.println("야옹");
    }

    void punch() {
        System.out.println("냥냥펀치");
    }
}

 

 

마치며

업캐스팅과 다운캐스팅을 알아봤다. 코드의 간결성과 유지보수성 향상, 다형성의 활용, 코드 재사용과 일관성, 확장성 등 다양한 장점을 가졌으므로 잘 알아두었다가 유용하게 사용하자.