[Java] 인터페이스 ④

kiteB·2022년 1월 9일
0

Java

목록 보기
20/35
post-thumbnail
post-custom-banner

[ 타입 변환과 다형성 ]

앞서 상속에 대해 공부할 때 타입 변환과 다형성에 대해 알아보았다.

🔗 참고 | 상속 - 타입 변환과 다형성

인터페이스도 상속과 마찬가지로 다형성을 구현하는 기술이 사용되며,
오히려 요즘에는 상속보다 인터페이스를 통해서 다형성을 구현하는 경우가 더 많다.

다형성
: 하나의 타입에 대입되는 객체에 따라서 실행 결과가 다양한 형태로 나오는 성질

부모 타입에 어떤 자식 객체를 대입하느냐에 따라 실행 결과가 달라지듯이,
인터페이스 타입에 어떤 구현 객체를 대입하느냐에 따라 실행 결과가 달라진다.

같은 종류의 하위 클래스를 만드느냐(상속), 사용 방법이 동일한 클래스를 만드느냐(인터페이스) 정도의 개념적 차이는 있지만, 상속과 인터페이스 모두 다형성을 구현하는 기술이라고 할 수 있다.

📌 인터페이스의 다형성
프로그램 소스 코드는 변함 없는데, 구현 객체를 교체함으로써 프로그램의 실행 결과가 달라지는 것을 말한다.

프로그램을 개발할 때 인터페이스를 사용해서 메소드를 호출하도록 코딩했다면,
구현 객체를 교체하는 것은 매우 손쉽고 빠르게 할 수 있다.

📌 인터페이스 매개변수의 다형성
인터페이스 타입으로 매개 변수를 선언하면 메소드 호출 시 매개값으로 여러 가지 종류의 구현 객체를 줄 수 있기 때문에 메소드 실행 결과가 다양하게 나온다.

예를 들어 useRemoteControl() 메소드의 매개변수가 RemoteControl 인터페이스 타입일 경우, 매개값으로 Television 객체 또는 Audio 객체를 선택적으로 줄 수 있다.

메소드 호출 시 어떤 구현 객체를 매개값으로 줄 수 있느냐에 따라 useRemoteControl() 메소드의 실행 결과는 다르게 나온다.


1. 자동 타입 변환 ( Promotion )

구현 객체가 인터페이스 타입으로 변환되는 것

인터페이스 구현 클래스를 상속해서 자식 클래스를 만들었다면 자식 객체 역시 인터페이스 타입으로 자동 타입 변환시킬 수 있다.

자동 타입 변환을 이용하면 필드의 다형성과 매개 변수의 다형성을 구현할 수 있다.
필드와 매개 변수의 타입을 인터페이스로 선언하면 여기에 다양한 구현 객체를 대입해서 실행 결과를 다양하게 만들 수 있다.


2. 필드의 다형성

자동차를 설계할 때 다음과 같이 필드 타입으로 타이어 인터페이스를 선언하게 되면 필드값으로 starTire, koreaTire 객체를 대입할 수 있다.

public class Car {
    Tire frontLeftTire = new starTire();
    Tire frontRightTire = new starTire();
    Tire backLeftTire = new starTire();
    Tire backRightTire = new starTire();
}

Car 객체를 생성한 후, 초기값으로 대입한 구현 객체 대신 다른 구현 객체를 대입할 수도 있다. (타이어 교체)

Car myCar = new Car();
myCar.frontLeftTire = new koreaTire();
myCar.frontRightTire = new koreaTire();

frontLeftTirefrontRightTire에 어떠한 타이어 구현 객체가 저장되어도
Car 객체는 타이어 인터페이스에 선언된 메소드만 사용하므로 전혀 문제가 되지 않는다.

Car 객체의 run() 메소드에서 타이어 인터페이스에 선언된 roll() 메소드를 호출해보자.

void run() {
    frontLeftTire.roll();
    frontRightTire.roll();
    backLeftTire.roll();
    backRightTire.roll();
}

frontLeftTirefrontRightTire를 교체하기 전에는 starTire 객체의 roll() 메소드가 호출되지만, koreaTire로 교체된 후에는 koreaTire 객체의 roll() 메소드가 호출된다.

Carrun() 메소드 수정 없이도 다양한 roll() 메소드의 실행 결과를 얻을 수 있게 되는 것이다! 이것이 바로 필드의 다형성이다.


3. 인터페이스 배열로 구현 객체 관리

위에서는 Car 클래스에서 4개의 타이어 필드를 인터페이스로 각각 선언했지만 다음과 같이 인터페이스 배열로 관리할 수도 있다.

Tire[] tires = {
    new starTire(),
    new starTire(),
    new starTire(),
    new starTire()
};

예를 들어 인덱스1을 활용해서 앞오른쪽 타이어(frontRightTire)를 koreaTire로 교체하려면 다음과 같이 작성하면 된다.

tires[1] = new koreaTire();

tires 배열의 각 항목은 Tire 인터페이스 타입이므로, 구현 객체인 koreaTire를 대입하면 자동 타입 변환이 발생하기 때문에 아무런 문제가 없다.


4. 매개 변수의 다형성

자동 타입 변환은 필드의 값을 대입할 때에도 발생하지만, 주로 메소드를 호출할 때 많이 발생한다.

매개값을 다양화하기 위해서

  • 상속에서는 매개 변수를 부모 타입으로 선언하고 호출할 때에는 자식 객체를 대입한다.
  • 인터페이스에서는 매개 변수를 인터페이스 타입으로 선언하고 호출할 때에는 구현 객체를 대입한다.

예제

Driver 클래스에는 drive() 메소드가 정의되어 있는데 Vehicle 타입의 매개 변수가 선언되어 있다.

  • Driver
public class Driver {
    public void drive(Vehicle vehicle) {
        vehicle.run();
    }
}
  • Vehicle (인터페이스)
public interface Vehicle {
    public void run();
}

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

drive() 메소드는 Vehicle 타입을 매개 변수로 선언했지만,
Vehicle을 구현한 Bus 객체가 매개값으로 사용되면 자동 타입 변환이 발생한다.

매개 변수의 타입이 인터페이스일 경우, 어떠한 구현 객체도 매개값으로 사용할 수 있고, 어떤 구현 객체가 제공되느냐에 따라 메소드의 실행 결과는 다양해질 수 있다. (매개 변수의 다형성)

예제

  • Driver (매개 변수의 인터페이스화)
public class Driver {
    public void drive(Vehicle vehicle) {
        vehicle.run();
    }
}
  • Vehicle (인터페이스)
public interface Vehicle {
    public void run();
}
  • Bus (구현 클래스)
public class Bus implements Vehicle {
    @Override
    public void run() {
        System.out.println("버스가 달립니다.");
    }
}
  • Taxi (구현 클래스)
public class Taxi implements Vehicle {
    @Override
    public void run() {
        System.out.println("택시가 달립니다.");
    }
}
  • DriveExample (매개 변수의 다형성 테스트)
public class DriverExample {
    public void static main(String[] args) {
        Driver driver = new Driver();
        
        Bus bus = new Bus();
        Taxi taxi = new Taxi();
        
        driver.drive(bus);	//자동 타입 변환 : Vehicle vehicle = bus;
        driver.drive(taxi);	//자동 타입 변환 : Vehicle vehicle = taxi;
    }
}
  • 실행 결과
버스가 달립니다.
택시가 달립니다.

5. 강제 타입 변환 ( Casting )

구현 객체가 인터페이스 타입으로 자동 변환하면, 인터페이스에 선언된 메소드만 사용 가능하다는 제약 사항이 따른다.

예를 들어 인터페이스에는 세 개의 메소드가 선언되어 있고, 클래스에는 다섯 개의 메소드가 선언되어 있다면, 인터페이스로 호출 가능한 메소드는 세 개뿐이다.

하지만 경우에 따라서는 구현 클래스에 선언된 필드와 메소드를 사용해야 할 경우도 발생한다.
이때 강제 타입 변환을 해서 다시 구현 클래스 타입으로 변환한 다음, 구현 클래스의 필드와 메소드를 사용할 수 있다.

예제

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

        vehicle.run();
        //vehicle.checkFare();	(x) vehicle 인터페이스에는 checkFare()가 없다.
        
        Bus bus = (Bus) vehicle;	//강제 타입 변환
        
        bus.run();
        bus.checkFare();	//Bus 클래스에는 checkFare()가 있다.
    }
}

6. 객체 타입 확인 ( instanceof )

강제 타입 변환은 구현 객체가 인터페이스 타입으로 변환되어 있는 상태에서 가능하다.
그러나 어떤 구현 객체가 변환되어 있는지 알 수 없는 상태에서 무작정 변환을 할 경우 ClassCastExcepion이 발생할 수도 있다.

예를 들어 Taxi 객체가 인터페이스로 변환되어 있을 경우, Bus 타입으로 강제 타입 변환하면
구현 클래스 타입이 다르므로 ClassCastException 예외가 발생한다.

Vehicle vehicle = new Taxi();
Bus bus = (Bus) vehicle;	//ClassCastException 발생

메소드의 매개 변수가 인터페이스로 선언된 경우, 메소드를 호출할 때 다양한 구현 객체들을 매개값으로 지정할 수 있다 (매개 변수의 다형성).

하지만 어떤 구현 객체가 지정될지 모르는 상황에서 다음과 같이 매개값을 Bus로 강제 타입 변환하면 ClassCastException이 발생할 수 있다.

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

상속에서 객체 타입을 확인하기 위해 instanceof 연산자를 사용했듯이, 인터페이스 타입에서도 사용할 수 있다.

예제

Vehicle 인터페이스 타입으로 변환된 Bus인지 확인하기 위한 코드는 다음과 같다.

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

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

다음과 같이 drive() 메소드에서 매개값이 Bus 객체인 경우, BuscheckFare() 메소드를 호출해야 한다면 Bus 타입으로 강제 타입 변환을 해야 한다. (Vehicle 인터페이스에는 checkFare() 메소드가 없기 때문)

매개값으로 어떤 구현 객체가 대입될지 모르기 때문에 instanceof 연산자로 Bus 타입인지 꼭 확인해야 한다.

public class Driver {
    public void drive(Vehicle vehicle) {
        if (vehicle instanceof Bus) {	//vehicle 매개 변수가 참조하는 객체가 Bus인지 조사
            Bus bus = (Bus) vehicle;	//Bus 객체일 경우, 안전하게 강제 타입 변환시킴
        }
    }
}

[ 참고자료 ]

이것이 자바다 책

profile
🚧 https://coji.tistory.com/ 🏠
post-custom-banner

0개의 댓글