우리는 상속에서 타입 변환과 다형성에 대해 살펴보았다. 인터페이스도 다형성을 구현하는 기술이 사용된다. 다형성은 하나의 타입에 대입되는 객체에 따라서 실행 결과가 다양한 형태로 나오는 성질을 말한다. 부모 타입에 어떤 자식 객체를 대입하느냐에 따라 실행 결과가 달라지듯이, 인터페이스 타입에 어떤 구현 객체를 대입하느냐에 따라 실행 결과가 달라진다.
프로그램을 개발할 때 인터페이스를 사용해서 메소드를 호출하도록 코딩을 했다면, 구현 객체를 교체하는 것은 매우 손쉽고 빠르게 할 수 있다. 프로그램 소스 코드는 변함이 없는데, 구현 객체를 교체함으로써 프로그램의 실행 결과가 다양해진다. 이것이 인터페이스의 다형성이다.
// 인터페이스
interface I {
void method1();
void method2();
}
// 프로그램
I i = new A(); // 오류가 나는 A
I i = new B(); // 수정한 B
// 수정이 필요 없음
i.method1();
i.method2();
예를 들어 위와 같이 I 인터페이스를 이용해서 프로그램을 개발하였다. I 인터페이스를 구현한 클래스로 처음에는 A 클래스를 선택한다. 개발 완료 후 테스트를 해보니 A 클래스에 문제가 생겨 다른 클래스를 사용해야 한다. 이런 경우 I 인터페이스를 구현한 B 클래스를 만들고 단 한줄만 수정해서 프로그램을 재실행할 수 있다.
인터페이스는 메소드의 매개 변수로 많이 등장한다. 인터페이스 타입으로 매개 변수를 선언하면 매소드 호출 시 매개값으로 여러 가지 종류의 구현 객체를 줄 수 있기 때문에 메소드 실행 결과가 다양하게 나온다. 이것이 인터페이스 매개 변수의 다형성이다. 예를 들어, useRemoteControl() 메소드의 매개 변수가 RemoteControl 인터페이스 타입일 경우, 매개값으로 Television 객체 또는 Audio 객체를 선택적으로 줄 수 있다.
자동 타입 변환(promotion)
구현 객체가 인터페이스 타입으로 변환되는 것은 자동 타입 변환(Promotion)에 해당한다. 자동 타입 변환은 프로그램 실행 도중에 자동적으로 타입 변환이 일어나는 것을 말한다. 인터페이스 구현 클래스를 상속해서 자식 클래스를 만들었다면 자식 객체 역시 인터페이스 타입으로 자동 타입 변환시킬 수 있다. 자동 타입 변환을 이용하면 필드의 다형성과 매개 변수의 다형성을 구현할 수 있다. 필드와 매개 변수의 타입을 인터페이스로 선언하면 여기에 다양한 구현 객체를 대입해서 실행 결과를 다양하게 만들 수 있다.
필드의 다형성
상속에서 다형성을 설명할 때 타이어를 예시로 설명했다. 상속에서는 타이어 클래스 타입에 HankookTire와 HumhoTire라는 자식 객체를 대입해서 교체할 수 있음을 보여주었지만, 지금은 클래스 타입이 아니고 인터페이스라는 점과 HankookTire와 HumhoTire는 자식 클래스가 아니라 구현 클래스라는 점이다.
HankookTire와 HumhoTire는 공통적으로 타이어 인터페이스를 구현했기 때문에 모두 타이어 인터페이스에 있는 메소드를 가지고 있다. 따라서 타이어 인터페이스로 동일하게 사용할 수 있는 교체 가능한 객체에 해당한다.
자동차를 설계할 때 다음과 같이 필드 타입으로 타이어 인터페이스를 선언하게 되면 필드값으로 HankookTire 또는 HumhoTire 객체를 대입할 수 있다. 자동 타입 변환이 일어나기 때문에 아무런 문제가 없다.
public class Car {
Tire frontLeftTire = new HankookTire();
Tire frontRightTire = new HankookTire();
Tire backLeftTire = new HankookTire();
Tire backRightTire = new HankookTire();
}
Car 객체를 생성한 후, 초기값으로 대입한 구현 객체 대신 다른 구현 객체를 대입할 수도 있다. 이것이 타이어 교체에 해당한다.
Car myCar = new Car();
myCar.frontLeftTire = new KumhoTire();
myCar.frontRightTire = new KumhoTire();
frontLeftTire와 frontRightTire에 어떠한 타이어 구현 객체가 저장되어도 Car 객체는 타이어 인터페이스에 선언된 메소드만 사용하므로 전혀 문제가 되지 않는다. 다음은 Car 객체의 run() 메소드에서 타이어 인터페이스에 선언된 roll() 메소드를 호출한다.
void run() {
frontLeftTire.roll();
frontRightTire.roll();
backLeftTire.roll();
backRightTire.roll();
}
frontLeftTire와 frontRightTire를 교체하기 전에는 HankookTire 객체의 roll() 메소드가 호출되지만, KumhoTire로 교체된 후에는 KumhoTire 객체의 roll() 메소드가 호출된다. Car의 run() 메소드 수정 없이도 다양한 roll() 메소드의 실행 결과를 얻을 수 있게 되는 것이다. 이것이 바로 필드의 다형성이다.
// Tire.java
public interface Tire {
public void roll();
}
// HankookTire.java
public class HankookTire implements Tire {
@Override
public void roll() {
System.out.println("한국 타이어가 굴러갑니다.");
}
}
// KumhoTire.java
public class KumhoTire impolements Tire {
@Override
public void roll() {
System.out.println("금호 타이어가 굴러갑니다.");
}
}
// Car.java
public class Car {
Tire frontLeftTire = new HankookTire();
Tire frontRightTire = new HankookTire();
Tire backLeftTire = new HankookTire();
Tire backRightTire = new HankookTire();
void run() {
frontLeftTire.roll();
frontRightTire.roll();
backLeftTire.roll();
backRightTire.roll();
}
}
// CarExample.java
public class CarExample {
public static void main(String[] args) {
Car myCar = new Car();
myCar.run();
myCar.frontLeftTire = new KumhoTire();
myCar.frontRightTire = new KumhoTire();
myCar.run();
}
}
인터페이스 배열로 구현 객체 관리
이전 예제에서는 Car 클래스에서 4개의 타이어 필드를 인터페이스로 각각 선언했지만 다음과 같이 인터페이스 배열로 관리할 수도 있다.
Tire[] tires = {
new HankookTire(),
new HankookTire(),
new HankookTire(),
new HankookTire()
};
frontLeftTire는 tires[0], frontRightTire는 tires[1], ... 과 같이 인덱스로 표현되므로 대입이나 제어문에서 활용하기 매우 쉽다. 예를 들어 인덱스 1을 이용해서 앞오른쪽 타이어를 KumhoTire로 교체하기 위해 다음과 같이 작성할 수 있다.
tires[1] = new KumhoTire();
tires 배열의 각 항목은 Tire 인터페이스 타입이므로, 구현 객체인 KumhoTire를 대입하면 자동 타입 변환이 발생하기 때문에 아무런 문제가 없다. 구현 객체들을 배열로 관리하면 제어문에서 가장 많이 혜택을 본다. 예를 들어 전체 타이어의 roll() 메소드를 호출하는 Car 클래스의 run() 메소드는 다음과 같이 for문으로 작성할 수 있다.
void run() {
for(Tire tire : tire) {
tire.roll();
}
}
다음은 이전 예제에서 작성한 Car 클래스의 타이어 필드를 배열로 수정한 예제이다.
// Car.java
public class Car {
Tire[] tires = {
new HankookTire(),
new HankookTire(),
new HankookTire(),
new HankookTire()
};
void run() {
for(Tire tire : tires) {
tire.roll();
}
}
}
// CarExample.java
public class CarExample {
public static void main(String[] args) {
Car myCar = new Car();
myCar.run();
myCar.tires[0] = new KumhoTire();
myCar.tires[1] = new KumhoTire();
myCar.run();
}
}
'Java 길찾기 > 이것이 자바다' 카테고리의 다른 글
[Java] 디폴트 메소드와 인터페이스 확장 (0) | 2022.02.04 |
---|---|
[Java] 인터페이스 상속 (0) | 2022.02.03 |
[Java] 인터페이스 (2) (0) | 2022.01.27 |
[Java] 인터페이스 (1) (0) | 2022.01.26 |
[Java] 추상 클래스 (0) | 2022.01.25 |