본문 바로가기

Java 길찾기/이것이 자바다

[Java] 인터페이스 - 타입 변환과 다형성 (2)

매개변수의 다형성

자동 타입 변환은 필드의 값을 대입할 때에도 발생하지만, 주로 메소드를 호출할 때 많이 발생한다. 매개값을 다양화하기 위해서 상속에서는 매개 변수를 부모 타입으로 선언하고 호출할 때에는 자식 객체를 대입했었다. 이번에는 매개 변수를 인터페이스 타입으로 선언하고 호출할 때에는 구현 객체를 대입한다. 예를 들어 다음과 같이 Driver 클래스에는 drive() 메소드가 정의되어 있는데 Vehicle 타입의 매개 변수가 선언되어 있다.

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

 

Vehicle을 다음과 같이 인터페이스 타입이라고 가정해보자.

public interface Vehicle {
    public void run();
}

만약 Bus가 구현 클래스라면 다음과 같이 Driver의 drive() 메소드를 호출할 때 Bus 객체를 생성해서 매개값으로 줄 수 있다.

Driver driver = new Driver();

Bus bus = new Bus();

driver.drive(Bus);  // 자동 타입 변환 발생, Vehicle vehicle = bus;

drive() 메소드는 Vehicle 타입을 매개 변수로 선언했지만, Vehicle을 구현한 Bus 객체가 매개값으로 사용되면 자동 타입 변환이 발생한다. 매개 변수의 타입이 인터페이스일 경우, 어떠한 구현 객체도 매개값으로 사용할 수 있고, 어떤 구현 객체가 제공되느냐에 따라 메소드의 실행 결과는 다양해질 수 있다.

 

 

강제 타입 변환(Casting)

구현 객체가 인터페이스 타입으로 자동 변환하면, 인터페이스에 선언된 메소드만 사용 가능하다는 제약 사항이 따른다. 예를 들어 인터페이스에는 세 개의 메소드가 선언되어 있고, 클래스에는 다섯 개의 메소드가 선언되어 있다면, 인터페이스로 호출 가능한 메소드는 세 개 뿐이다. 하지만 경우에 따라서는 구현 클래스에 선언된 필드와 메소드를 사용해야 할 경우도 발생한다. 이때 강제 타입 변환을 해서 다시 구현 클래스 타입으로 변환한 다음, 구현 클래스의 필드와 메소드를 사용할 수 있다.

구현클래스 변수 = (구현클래스) 인터페이스 변수; // 구현클래스로 강제 타입 변환
// Vehicle.java
public interface Vehicle {
    public void run();
}
// Bus.java
public class Bus implements Vehicle {
    @Override
    public void run() {
        System.out.println("버스가 달립니다.");
    }
    
    public void checkFare() {
        System.out.println("승차요금을 체크합니다.");
    }
}
// VehicleExample.java
public class VehicleExample {
    public static void main(String[] args) {
        Vehicle vehicle = new Bus();
        
        vehicle.run();
        // vehicle.checkFare(); // (x) -- Vehicle 인터페이스에 checkFare()가 없기 때문
        
        bus.run;
    }
}

 

 

객체 타입 확인(instancof)

강제 타입 변환은 구현 객체가 인터페이스 타입으로 변환되어 있는 상태에서 가능하다. 그러나 어떤 구현 객체가 변환되어 있는지 알 수 없는 상태에서 무작정 변환을 할 경우 예외가 발생할 수도 있다. 예를 들어 다음과 같이 Taxi 객체가 인터페이스로 변환되어 있을 경우, Bus 타입으로 강제 타입 변환하면 구현 클래스 타입이 다르므로 예외가 발생한다.

Vihicle vehicle = new Taxi();
Bus bus = (Bus) vehicle;

 

메소드의 매개 변수가 인터페이스로 선언된 경우, 메소드를 호출할 때 다양한 구현 객체들을 매개값으로 지정할 수 있다. 어떤 구현 객체가 지정될지 모르는 상황에서 다음과 같이 매개값을 Bus로 강제 타입 변환하면 에러가 발생할 수 있다.

public void drive(Vehicle vehicle) {
    Bus bus = (Bus) vehicle;
    bus.checkFare();
    vehicle.run();
}

 

그렇다면 어떤 구현 객체가 인터페이스 타입으로 변환되었는지 확인하는 방법은 없을까? 우리는 상속에서 객체 타입을 확인하기 위해 instanceof 연산자를 사용했다. instanceof 연산자는 인터페이스 타입에서도 사용할 수 있다. 예를 들어 Vehicle 인터페이스 타입으로 변환된 객체가 Bus인지 확인하려면 다음과 같이 작성하면 된다.

if( vehicle instanceof Bus ) {
    Bus bus = (Bus) vehicle;
}

 

인터페이스 타입으로 자동 변환된 매개값을 메소드내에서 다시 구현 클래스 타입으로 강제 타입 변환해야 한다면 반드시 매개값이 어떤 객체인지 instanceof 연산자로 확인하고 안전하게 강제 타입 변환을 해야 한다.

다음과 같이 drive() 메소드에서 매개값이 Bus 객체인 경우, Bus의 checkFare() 메소드를 호출해야 한다면 Bus 타입으로 강제 타입 변환을 해야 한다. Vehicle 인터페이스에는 checkFare() 메소드가 없기 때문이다. 매개값으로 어떤 구현 객체가 대입될지 모르기 때문에 instanceof 연산자로 Bus 타입인지 꼭 확인해야 한다.

// Driver.java
public class Driver {
    public void drive(Vehicle vehicle) {
        if(vehicle instanceof Bus) {
            Bus bus = (Bus) vehicle;
            
            bus.checkFare();
        }
        vehicle.run();
    }
}