본문 바로가기

Java

[Java] 다형성에 대한 공부

다형성(Polymorphism)

하나의 객체가 여러 가지 타입을 가질 수 있는 것을 의미한다. 클래스가 상속 관계에 있을 때 나타나는 성질이다.

public class Car {

    public void carMethod(){
        System.out.println("자동차입니다.");
    }
}

public class OilCar extends Car{

    public void oilCarMethod(){
        System.out.println("기름차입니다.");
    }

}

위 코드는 OilCar가 Car를 상속하는 코드이다.

public class PloyMain {

    public static void main(String[] args) {

        System.out.println("Car - new Car");
        Car car = new Car();
        car.carMethod();

        System.out.println("OilCar - new OilCar");
        OilCar oilCar = new OilCar();
        oilCar.carMethod();
        oilCar.oilCarMethod();

        System.out.println("Car - new OilCar");
        Car polyCar=new OilCar();
        polyCar.carMethod();
    }
}

위 코드는 Car, OilCar클래스 코드를 이용한 코드이다. 이때 Car polyCar=new OilCar() 부분을 보면 Car 변수가 OilCar 인스턴스를 참조한다. 이는 Car클래스가 부모 클래스였기에 가능하다. 반대로 Olicar 변수가 Car인스턴스를 참조하지 못한다. 

코드를 실행하면 위처럼 결과가 나온다. 

코드는 위 그림처럼 객체가 생성된다. 그림에서 보듯이 oilCar에서 carMethod를 호출할 수 있다. 이는 상속으로 인해 메모리에서 OilCar와 Car 메서드가 생성했기 때문이다. 그림을 보면 특이한 점이 있는데 바로 polyCar이다. polyCar는 Car타입의 변수이지만 OilCar 인스턴스를 참조한다. 그래서 그림에서 인스턴스 메모리를 보면 Car, OilCar가 있다. 하지만 polyCar는 Car변수이기에 Car에 있는 메서드 밖에 호출하지 못한다. 실제로 OilCar에 있는 oilCarMethod를 호출하려 하면 인식을 하지 못한다. 

 

그러면 만약에 polyCar에서 Car와 OilCar가 같은 메서드를 가지고 있다면 어떻게 될까? 

public class Car {

    public void carMethod(){
        System.out.println("자동차입니다.");
    }
}

public class OilCar extends Car{

    public void oilCarMethod(){
        System.out.println("기름차입니다.");
    }

    @Override
    public void carMethod(){
        System.out.println("기름차입니다. - car");
    }
}

위 코드는 OilCar에 carMethod를 오버라이딩 했다. 이 상태에서 위에 PolyMain코드를 실행하면 

위와 같은 결과가 나온다. 이때는 결과값이 오버라이딩되어서 OilCar에 carMethod가 호출된다. 

또한 polyCar도 위 그림처럼 Car의 oilCarMethod 메서드가 있다면 oilCarMethod를 polyCar에서 호출할 수 있다.

 

 

그러면 polyCar는 다른 OilCar메서드를 호출할 수 없나?

polCar의 OilCar메서드를 호출하기 위해서는 업 캐스팅과 다운 캐스팅에 대해서 알아야 한다. 

 

업 캐스팅

현재 타입을 부모 타입으로 변경하는 것이다.

public class Car {

    public void carMethod(){
        System.out.println("자동차입니다.");
    }

    public void move(){ //추가한 코드
        System.out.println("움직입니다.");
    }
}

public class CastingMain {
    public static void main(String[] args) {
        OilCar oilCar =new OilCar();
        Car car1=(Car) oilCar; // 업 캐스팅
        Car car2=oilCar; // 업 캐스팅은 생략이 가능하다.

        car1.move();
        car2.move();
        ((Car) oilCar).move(); //일시적 업 캐스팅
    }
}

위 코드는 Car에 새로운 메서드인 move와 실행코드이다.

위 코드처럼 업 캐스팅은 생략이 가능하다. 또한 일시적 업 캐스팅으로 메서드를 호출하는 순간 업 캐스팅이 가능하다.

코드를 실행하면 위와 같은 결과가 나온다. 

그림처럼 업 캐스팅을 할 때는 메모리 상에 부모의 인스턴스가 존재하기에 안전하다. 

 

다운 캐스팅

현재 타입을 자식 타입으로 변경하는 것이다.

public class CastingMain {
    public static void main(String[] args) {

        Car polyCar = new OilCar();
//        Car car = new Car();

        OilCar oilCar =(OilCar) polyCar;
//        OilCar ErrorCar =(OilCar) car; 


        oilCar.oilCarMethod();
        ((OilCar) polyCar).oilCarMethod();
//        ErrorCar.oilCarMethod();
    }
}

위 코드는 Car에 다운 캐스팅을 하는 코드이다. 이때 OilCar의 인스턴스를 사용한다.

위 코드처럼 다운 캐스팅은 생략이 불가능하다. 또한 일시적 다운 캐스팅으로 메서드를 호출하는 순간 다운 캐스팅이 가능하다.

코드를 실행하면 위와 같은 결과가 나온다.

주석처리된 코드를 풀어서 실행을 하면 어떻게 될까?

public class CastingMain {
    public static void main(String[] args) {

        Car polyCar = new OilCar();
        Car car = new Car();

        OilCar oilCar =(OilCar) polyCar;
        OilCar ErrorCar =(OilCar) car;


        oilCar.oilCarMethod();
        ((OilCar) polyCar).oilCarMethod();
        ErrorCar.oilCarMethod();
    }
}

주석을 풀고 위코드를 실행해 보자.

위와 같은 ClassCastException가 발생한다. ClassCastException은 객체를 호환되지 않는 클래스로 형변환하려고 할 때 발생하는 일반적인 런타임 예외이다. 위 코드에 상황을 그림으로 좀 더 자세하게 살펴보자

그림처럼 다운 캐스팅을 할 때는 메모리 상에 자식의 인스턴스가 없기에 ClassCastException가 발생한다. 예시처럼 실제로 코딩할 때 메모리상에 자식 인스턴스가 존재하는지 알지 못하기에 다운 캐스팅은 위험하다. 

 

 

그러면 다운 캐스팅을 사용하기 위해서는 어떻게 해야 할까?

instanceof

인스턴스 변수가 참조하는 인스턴스의 타입을 확인하는 키워드이다.

public class CastingMain {
    public static void main(String[] args) {

        Car polyCar = new OilCar();
        System.out.println("Car - new OilCar");
        oilCheck(polyCar);

        Car car = new Car();
        System.out.println("Car - new Car");
        oilCheck(car);
    }

    private static void oilCheck(Car car){
        if(car instanceof OilCar){
            System.out.println("OilCar 메서드 호출");
            OilCar oilCar=(OilCar) car;
            oilCar.oilCarMethod();
        }
    }
}

위 코드는 인스턴스 변수의 참조타입이 OilCar이면 oilCarMethod를 실행하는 코드이다.

위 결과에서 보이듯이 polyCar에서는 메모리 상에 OilCar 인스턴스가 있어서 oilCarMethod를 실행했다. 이를 통해서 메모리 상에 원하는 인스턴스가 있는지 알 수 있어 안전하게 다운 캐스팅을 할 수 있다.

 

 

 

출처 

https://www.inflearn.com/course/%EA%B9%80%EC%98%81%ED%95%9C%EC%9D%98-%EC%8B%A4%EC%A0%84-%EC%9E%90%EB%B0%94-%EA%B8%B0%EB%B3%B8%ED%8E%B8#reviews