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

Devlog·2024년 3월 20일

Java

목록 보기
21/41

✔️ 인터페이스의 다형성

: 프로그램 소스 코드는 변함이 없는데,
  구현 객체를 교체함으로써 프로그램의 실행결과가 다양해짐

: 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() 실행

0개의 댓글