: 프로그램 소스 코드는 변함이 없는데,
구현 객체를 교체함으로써 프로그램의 실행결과가 다양해짐
: I 인터페이스를 이용해서 프로그램을 개발함
: I 인터페이스를 구현한 클래스로
처음에는 A 클래스를 선택했는데,
테스트해보니 A 클래스에 문제가 있다는 것을 알게됨
: 그래서 B 클래스와 교체한 후 단 한 줄만 수정해서
프로그램을 재실행함
인터페이스 변수= (자동 타입 변환) 구현객체;
: 구현 객체가 인터페이스 타입으로 변환되는 것
: 프로그램 실행 도중에자동적으로 타입 변환이 일어나는 것
: 인터페이스 구현 클래스를 상속해서 자식 클래스를 만들었다면
자식 객체 역시 인터페이스 타입으로 자동 타입 변환할 수 있음
B b = new B();
C c = new C();
D d = new D();
E e = new E();
↓
A a1 = b; // 가능
A a2 = c; // 가능
A a3 = d; // 가능
A a4 = e; // 가능
: 자동 타입 변환을 이용하면
필드의 다형성과 매개 변수의 다형성을 구현할 수 있음
: 필드와 매개변수의 타입을 인터페이스로 선언하면
다양한 구현 객체를 대입해서 실행결과를 다양하게 만들 수 있음
ex)
자동차는 타이어 인터페이스를 이용해서
동일한 방법으로 한국 타이어와
금호 타이어를 사용함
자동차를 설계할 때
필드 타입으로 타이어 인터페이스를 선언하면
필드값으로 한국 타이어 또는 금호 타이어 객체를 대입할 수 있음
자동 타입 변환이 일어나기 때문에 아무런 문제가 없음
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와 frontRight를 교체하기 전에는
HankookTire 객체의 roll() 메소드가 호출되지만,
KumhoTire로 교체된 후에는 KumhoTire 객체의 roll() 메소드가 호출됨
• 필드의 다형성
: Car의 run() 메소드를 수정하지 않아도
댜양한 roll() 메소드의 실행결과를 얻을 수 있게 됨
// 인터페이스
public interface Tire {
// roll() 메소드 호출 방법 설명
public void roll();
}
// 구현클래스
public class HankookTire implements Tire {
// Tire 인터페이스 구현
@Override
public void roll() {
System.out.println("한국 타이어가 굴러갑니다.");
}
}
// 구현 클래스
public class KumhoTire implements Tire{
@Override
public void roll() {
System.out.println("금호 타이어가 굴러갑니다.");
}
}
// 필드 다형성
public class Car {
// 인터페이스 타입 필드 선언과
// 초기 구현 객체 대입
Tire frontLeftTire = new HankookTire();
Tire frontRightTire = new HankookTire();
Tire backLeftTire = new HankookTire();
Tire backRightTire = new HankookTire();
// 인터페이스에서 설명된
// roll()메소드 호출
void run() {
frontLeftTire.roll();
frontRightTire.roll();
backLeftTire.roll();
backRightTire.roll();
}
}
// 필드 다형성 테스트
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();
}
}
💻 결과
한국 타이어가 굴러갑니다.
한국 타이어가 굴러갑니다.
한국 타이어가 굴러갑니다.
한국 타이어가 굴러갑니다.
금호 타이어가 굴러갑니다.
금호 타이어가 굴러갑니다.
한국 타이어가 굴러갑니다.
한국 타이어가 굴러갑니다.
ex)
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 );
drive() 메소드는 Vehicle 타입을 매개 변수로 선언했지만,
Vehicle을 구현한 Bus 객체가 매개값으로 사용되면
자동 타입 변환이 발생함
Vehicle vehicle = (자동 타입 변환) bus;
• 매개변수의 다형성
: 매개 변수의 타입이 인터페이스일 경우
어떠한 구현 객체도 매개값으로 사용할 수 있고,
어떤 구현 객체가 제공되느냐에 따라
메소드의 실행결과는 다양해질 수 있음
void drive(Vehicle vehicle) {
//vehicle → 구현 객체
// 구현 객체의 run()메소드가 실행됨)
vehicle.run();
}
: useRemoteControl() 메소드의 매개 변수가
RemoteControl 인터페이스 타입일 경우,
매개값으로 Television 객체 또는 Audio 객체를 선택적으로 줄 수 있음
: 메소드 호출 시 어떤 구현 객체를 매개값으로 주느냐에 따라서
useRemoteControl() 메소드의 실행결과는 다르게 나옴
: 인터페이스는 메소드의 매개 변수로 많이 등장함
: 인터페이스 타입으로 매개 변수를 선언하면
메소드 호출 시
매개값으로 여러 가지 종류의 구현 객체를
줄 수 있기 때문에 메소드 실행결과가 다양하게 나옴
//매개 변수의 인터페이스화
public class Driver {
public void drive(Vehicle vehicle) {
vehicle.run();
}
}
//인터페이스
public interface Vehicle {
public void run();
}
//구현 클래스
public class Bus implements Vehicle {
@Override
public void run() {
System.out.println("버스가 달립니다.");
}
}
//구현 클래스
public class Taxi implements Vehicle{
@Override
public void run() {
System.out.println("택시가 달립니다.");
}
}
//매개 변수의 다형성 테스트
public class DriverExample {
public static void main(String[] args) {
Driver driver = new Driver();
Bus bus = new Bus();
Taxi taxi = new Taxi();
//자동 타입 변환: Vehicle vehicle = bus;
driver.drive(bus);
//자동 타입 변환: Vehicle vehicle = taxi;
driver.drive(taxi);
}
}
구현클래스 변수 = (구현클래스) 인터페이스변수; [강제 타입 변환]
: 구현 객체가 인터페이스 타입으로 자동 타입 변환하면,
인터페이스에 선언된 메소드만 사용 가능하다는 제약 사항이 따름
ex)
인터페이스 3개의 메소드가 선언되어 있고
클래스에는 5개의 메소드가 선언되어 있다면,
인터페이스로 호출 가능한 메소드는 3개뿐임
: 상황에 따라서 구현 클래스에 선언된
필드와 메소드를 사용해야 할 경우도 발생함
: 이때 강제 타입 변환을 해서 다시 구현 클래스 타입으로 변환한 다음,
구현 클래스의 필드와 메소드를 사용할 수 있음
//인터페이스
public interface Vehicle {
public void run();
}
//구현 클래스
public class Bus implements Vehicle {
@Override
public void run() {
System.out.println("버스가 달립니다.");
}
public void checkFare() {
System.out.println("승차요금을 체크합니다.");
}
}
// 강제타입변환
public class VehicleExample {
public static void main(String[] args) {
Vehicle vehicle = new Bus();
vehicle.run();
//Vehicle 인터페이스에는 checkFare()가 없음
//vehicle.checkFare();(x)
//강제 타입 변환
Bus bus = (Bus) vehicle;
bus.run();
//Bus클래스에는 checkFare()가 있음
bus.checkFare();
}
}
: 어떤 구현 객체가 변환되어 있는지 알 수 없는 상태에서
무작정 강제 타입 변환할 경우 ClassCastException이 발생함
ex)
Taxi 객체가 인터페이스로 변환되어 있을 경우,
Bus 타입으로 강제 타입 변환하면
구현 클래스 타입이 다르므로 ClassCastException이 발생함
Vehicle vehicle = new Taxi();
Bus bus = (Bus) vehicle;
: 메소드의 매개 변수가 인터페이스로 선언된 경우,
메소드를 호출할 때 다양한 구현 객체들을 매개값으로 지정 가능
: 어떤 구현 객체가 지정될지 모르는 상황에서
다음과 같이 매개값을 Bus로 강제 타입 변환하면
ClassCastException이 발생함
ex)
public void drive(Vehicle vehicle) {
Bus bus = (Bus) vehicle;
bus.checkFare();
vehicle.run();
}
:어떤 구현 객체가 인터페이스 타입으로 변환되었는지 확인하는 방법은
instanceof연산자 이용하면 됨
ex)
Vehicle 인터페이스 타입으로 변환된 객체가
Bus인지 확인하는 방법
if(vehicle instanceof Bus) {
Bus bus = (Bus) vehicle;
}
: 인터페이스 타입으로 자동 타입 변환된 매개값을
메소드 내에서 다시 구현 클래스 타입으로
강제 타입 변환해야 한다면 반드시 매개값이 어떤 객체인지
instanceof 연산자로 확인하고 안전하게 강제 타입 변환 해야함
👩💻 객체 타입 확인
public class Driver {
// vehicle → Bus객체, Taxi객체
public void drive(Vehicle vehicle) {
// vehicle 매개변수가 참조하는 객체가 Bus인지 조사
if (vehicle instanceof Bus) {
// Bus객체일 경우 안전하게 강제 타입 변환
Bus bus = (Bus) vehicle;
// Bus타입으로 강제 타입 변환을 하는 이유
bus.checkFare();
}
vehicle.run();
}
}
public class DriverExample {
public static void main(String[] args) {
Driver driver = new Driver();
Bus bus = new Bus();
Taxi taxi = new Taxi();
driver.drive(bus);
driver.drive(taxi);
}
}
💻 결과
승차요금을 체크합니다.
버스가 달립니다.
택시가 달립니다.
public interface 하위인터페이스 extends
상위인터페이스1, 상위인터페이스2 { ··· }
: 클래스와는 달리 다중 상속을 허용함
: 하위 인터페이스를 구현하는 클래스는
하위 인터페이스의 메소드뿐만 아니라
상위 인터페이스의 모든 추상 메소드에 대한
실체 메소드를 가지고 있어야함
: 그렇기 때문에 구현 클래스로부터 객체를 생성한 후
하위 및 상위 인터페이스 타입으로 변환이 가능함
하위인터페이스 변수 = new 구현클래스( ··· );
상위인터페이스1 변수 = new 구현클래스( ··· );
상위인터페이스2 변수 = new 구현클래스( ··· );
: 하위 인터페이스로 타입 변환이 되면
상위 및 하위 인터페이스에 선언된
모든 메소드를 사용할 수 있으나,
상위 인터페이스로 타입 변환되면
상위 인터페이스에 선언된 메소드만 사용 가능하고
하위 인터페이스에 선언된 메소드는 사용할 수 없음
InterfaceC 인터페이스 변수는
methodA(), methodB(), methodC()를 모두 호출할 수 있지만,
InterfaceA와 InterfaceB 변수는
methodA()와 methodB()만 호출할 수 있음
//상위 인터페이스
public interface InterfaceA {
public void methodA();
}
//상위 인터페이스
public interface InterfaceB {
public void methodB();
}
//하위 인터페이스
public interface InterfaceC extends InterfaceA, InterfaceB {
public void methodC();
}
//하위 인터페이스 구현
public class ImplementationC implements InterfaceC {
public void methodA() {
System.out.println("ImplementationC-methodA() 실행");
}
public void methodB() {
System.out.println("ImplementationC-methodB() 실행");
}
public void methodC() {
System.out.println("ImplementationC-methodC() 실행");
}
}
//호출 가능 메소드
public class Example {
public static void main(String[] args) {
ImplementationC impl = new ImplementationC();
InterfaceA ia = impl;
ia.methodA();
//InterfaceA 변수는 methodA()만 호출가능
System.out.println();
InterfaceB ib = impl;
ib.methodB();
//InterfaceB 변수는 methodB()만 호출가능
System.out.println();
InterfaceC ic = impl;
//InterfaceC 변수는
//methodA(), methodB(), methodC() 모두 호출 가능
ic.methodA();
ic.methodB();
ic.methodC();
}
}
💻 결과
ImplementationC-methodA() 실행
ImplementationC-methodB() 실행
ImplementationC-methodA() 실행
ImplementationC-methodB() 실행
ImplementationC-methodC() 실행