본문 바로가기
👨‍🏫Study/JAVA

[JAVA] 08 - 2 타입 변환과 다형성

by 코푸는 개발자 2022. 3. 21.
728x90

 

인터페이스의 다형성

 

- 다형성을 구현하기 위해 필요한 2가지 : 메소드 재정의와 타입 변환

- 상속과 마찬가지로 인터페이스에서도 이 두 가지 기능이 제공됨

- 상속과 차이점 : 상속은 같은 종류의 하위 클래스 생성, 인터페이스는 사용 방법이 동일한 클래스를 만드는 기술

 

- 인터페이스의 다형성이 필요한 이유 : 소스 코드의 변함 없이 구현 객체를 빠르고 쉽게 교체하기 위해서이다

- 이를 통해 구현 객체를 쉽게 교체하여 실행 결과가 다양해져 인터페이스의 다형성을 이루게 됨

 

- 구현 객체 교체 예시

interface I { // 인터페이스 생성
	void method1();
    void method2();
}

// I i = new A(); // 이 코드를 아래 코드로 수정함
I i = new B();

i.method1(); // 이 부분을 수정하지 않아도 됨
i.method2();

 

 

자동 타입 변환

 

[인터페이스] [변수명] = 구현객체; // 자동 타입 변환

- 추가로, 인터페이스 구현 클래스를 상속한 자식 클래스가 만들어졌다면 그 자식 객체 역시 인터페이스 타입으로 자동 타입 변환 가능하다.

 

- 자동 타입 변환을 이용하여 필드나 매개변수의 다형성을 구현할 수 있다.

- 필드와 매개변수의 타입으로 인터페이스를 사용하면 다양한 구현 객체를 대입하여 실행 결과를 다양하게 할 수 있음

 

필드의 다형성 예제

 

- 구성 : 

인터페이스 : Tire

구현 클래스 : Tire HankookTire와 KumhoTire 

필드 선언 클래스 : Car 

실행 코드 : CarExample

 

<인터페이스>

interface Tire {
	// 추상 메소드
	public void roll(); // roll() 메소드 호출 방법 설명
}

 

<구현 클래스>

class HanKookTire implements Tire {
	@Override
	public void roll() {
		System.out.println("한국 타이어가 굴러갑니다.");
	}
}

class KumhoTire implements Tire {
	@Override
	public void roll() {
		System.out.println("금호 타이어가 굴러갑니다.");
	}
}

 

<필드의 다형성>

class Car {
	// 필드 타입으로 Tire 인터페이스 사용 
	// 필드 값으로 한국 타이어나 금호 타이어 객체 대입 가능 
	// 자동 타입 변환이 일어남
	Tire frontLeftTire = new HanKookTire();
	Tire frontRightTire = new HanKookTire();
	Tire backLeftTire = new HanKookTire();
	Tire backRightTire = new HanKookTire();
	
	// Car 객체의 run() 메소드에서 Tire 인터페이스에서 선언된 roll() 메소드 호출
	void run() {
		frontLeftTire.roll();
		frontRightTire.roll();
		backLeftTire.roll();
		backRightTire.roll();
	}
}

 

<실행 코드>

public class CarExample {
	public static void main(String[] args) {
		// Car 객체 생성 
		Car myCar = new Car();
		
		myCar.run(); // 교체 전이라 모두 한국타이어가 뜸
		System.out.println();
		
		// 초기값으로 대입한 구현 객체 대신 다른 구현 객체를 대입할 수도 있음
		// 이것이 바로 타이어 교체
		myCar.frontLeftTire = new KumhoTire();
		myCar.frontRightTire = new KumhoTire();
		
		myCar.run(); // 앞 바퀴 교체 후 금호타이어로 바뀜
		System.out.println();
		
		// run() 메소드를 수정하지 않아도 다양한 roll() 메소드의 실행 결과를 얻게 됨
		// 이것이 필드의 다형성
	}
}

 

매개 변수의 다형성

 

- 메소드의 매개변수 타입으로 인터페이스를 사용할 수 있는데 그 경우에는 구현 객체를 매개값으로 사용할 수 있다.

- 그러면 어떤 구현 객체를 매개값으로 사용하냐에 따라 메소드의 실행 결과는 다양해진다.

- 이것이 바로 인터페이스의 매개 변수의 다형성이다.

 

매개 변수의 다형성 예제

 

<인터페이스>

interface Vehicle { 
	public void run(); // 추상 메소드
}

 

<매개변수로 인터페이스 사용>

class Driver {
	public void drive(Vehicle vehicle) { 
    	// main 메소드에서 vehicle 자리에 Bus 객체와 Taxi 객체를 보내줄 예정
		vehicle.run();
	}
}

 

<구현 클래스>

// 구현 클래스
class Bus implements Vehicle {
	@Override
	public void run() {
		System.out.println("버스가 달립니다.");
	}
}

//구현 클래스
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();
		
		driver.drive(bus); // 버스가 달립니다.
		driver.drive(taxi); // 택시가 달립니다.
	}
}

 

 

강제 타입 변환 ( + 객체 타입 확인)

 

<인터페이스>

interface Vehicle1 { 
	public void run();
}

 

<구현 클래스>

class Bus1 implements Vehicle1 {
	@Override
	public void run() { // 인터페이스의 추상 메소드를 재정의한 실체 메소드
		System.out.println("버스가 달립니다.");
	}
	
	public void checkFare() { // 인터페이스에 없는 새로운 메소드 선언
		System.out.println("승차요금을 체크합니다.");
	}
}

 

<실행 코드>

public class VehicleExample {
	public static void main(String[] args) {
		Vehicle1 vehicle = new Bus1(); // 자동 형변환
		
		vehicle.run(); // 실체 메소드가 실행됨
		// vehicle1.checkFare(); // Vehicle 인터페이스에 없는 메소드 호출시 컴파일 에러

		Bus1 bus = (Bus1)vehicle; // 강제 형변환
		
		bus.run(); 
		bus.checkFare(); // Bus 클래스에 있는 메소드이므로 호출 가능
	}
}

 

<출력 결과>

버스가 달립니다.
버스가 달립니다.
승차요금을 체크합니다.

 

 

객체 타입 확인 

 

- 버스 타입인지 택시 타입인지 모르는 상태에서 버스 타입으로 강제 형변환하면 ClassCastException 에러가 발생함

- 매개 값으로 들어올 때 어떤 구현 객체가 인터페이스 타입으로 변환되었는지 확인하고자함

- 인터페이스 타입(Vehicle) 으로 자동 형변환된 매개값을 다시 구현 클래스 타입 (Bus)로 강제 타입 변환하고자 할때 반드시 그 객체가 Bus타입인지 Taxi 타입인지 확인을 해야한다.

 - if문을 이용하여 instanceof 연산자로 확인한다.

 - 이것을 하는 근본적인 이유는 checkFare() 메소드가 Bus타입에만 있고 인터페이스 타입에는 없어서 이 메소드를 사용하고 싶다면 다시 Bus 타입으로 강제형변환을 해줘야한다. 그 과정에서 이렇게 확인 작업이 필요하다.

 

객체 타입 확인 예제 ( instanceof 연산자)

 

<객체 타입 확인>

class Driver {
	public void drive(Vehicle vehicle) { // main 메소드에서 vehicle 자리에 Bus 객체와 Taxi 객체를 보내줄 예정
		if(vehicle instanceof Bus) { 
			Bus bus = (Bus) vehicle; // 매개 값으로 Bus 객체가 들어온다면 강제 타입 변환 가능
			bus.checkFare(); // Bus타입으로 강제형변환을 한 이유로서 Bus 클래스의 메소드를 쓰기위함이다. 
			// ((Bus)vehicle).checkFare(); // 이렇게 한 줄로 쓸 수도 있음
			vehicle.run();
		} else {
			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); // 승차요금을 체크합니다. 버스가 달립니다.
		// drive() 메소드의 매개 값으로 bus를 대입했으므로 Bus 타입으로 형변환 가능
		driver.drive(taxi); // 택시가 달립니다.
		// taxi는 Bus 타입으로 형변환 안되서 checkFare() 출력안됨
		
		Vehicle vehicle = new Bus(); // 자동 형변환
	}
}

 

<출력 결과>

승차요금을 체크합니다. // Bus 객체가 들어가서 형변환 가능하여 if문 안의 checkFare() 메소드 실행된 결과임
버스가 달립니다. //  Bus 객체가 들어가서 형변환 가능하여 if문 안의 run() 메소드 실행된 결과임
택시가 달립니다. // Taxi 객체가 들어가서 형변환 불가능하여 else문 안의 run() 메소드 실행된 결과임

 

 

인터페이스 상속

 

- 인터페이스끼리도 상속이 가능하다.

- 클래스의 상속과 달리 인터페이스 상속은 다중 상속이 가능하다.

 

- 구현 클래스는 하위 인터페이스를 구현하는 것 일지라도 상위 인터페이스의 실체 메소드까지 모두 가지고 있어야 한다.

- 따라서 하위 인터페이스를 구현하더라도 상위 인터페이스까지 new 연산자를 통해 객체를 생성하여 자동 타입 변환이 가능하다.

 

- 하위 인터페이스로 타입이 변환되면 상위 인터페이스의 모든 메소드까지 전부 사용 가능하다.

- 상위 인터페이스로 타입 변환이 되면 하위 인터페이스에 선언된 메소드는 사용 불가

 

인터페이스 상속 예제

 

<상위 인터페이스>

interface InterfaceA {
	public void methodA();
 }
interface InterfaceB {
	public void methodB();
}

 

<하위 인터페이스>

interface InterfaceC extends InterfaceA, InterfaceB {
	public void methodC();
}

 

<하위 인터페이스 구현>

class ImplementationC implements InterfaceC {
	public void methodA() { // InterfaceC와 그 위에 있는 모든 상위 인터페이스들의 실체 메소드들도 있어야함
		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();
		System.out.println(); // methodA()만 호출 가능
		
		InterfaceB ib = impl;
		ib.methodB();
		System.out.println(); // methodB()만 호출 가능
		
		InterfaceC ic = impl;
		ic.methodA();	// InterfaceC 변수는  세 메소드 모두 호출 가능
		ic.methodB();
		ic.methodC();	
	}
}

 

728x90

댓글