Java Category/Java

[JAVA] 인터페이스 - 타입 변환과 다형성, 상속

ReBugs 2023. 1. 11.

본 게시글은 혼자 공부하는 자바 (저자 : 신용권)의 책과 유튜브 영상을 참고하였고, 개인적으로 정리하는 글임을 알립니다.


다형성을 구현하기 위해서는 메소드 재정의와 타입 변환이 필요하다. 인터페이스 역시 이 두 가지 기능이 제공되므로 상속과 더불어 다형성을 구현하는 데 많이 사용된다. 상속은 같은 종류의 하위 클래스를 만드는 기술이고, 인터페이스는 사용 방법이 동일한 클래스를 만드는 기술이라는 개념상 차이가 있지만 둘 다 다형성을 구현하는 방법은 비슷하다.

 

프로그램 소스 코드는 변함이 없는데, 구현 객체(클래스)를 교체함으로써 프로그램의 실행결과가 다양해지는 것을 인터페이스의 다형성이다.

위 그림에서 개발코드에서는 인터페이스를 통해서 객체 1을 사용하고 있었는데, 객체 1에 문제가 있음을 확인하면 객체 1의 사용을 중지하고 개발 코드에서 간단한 수정만 해서 객체 2를 사용하면 된다.

 

자동 타입 변환

구현 객체가 인터페이스 타입으로 변환되는 것은 자동 타입 변환에 해당한다.

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

  • D와 E는 구현 클래스가 아님에도 타입 변환이 가능한 이유는 각각의 부모 클래스가 인터페이스의 구현 클래스이기 때문에, 각각의 부모 클래스로부터 상속을 받았기 때문이다.
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; //가능

 

필드의 다형성

Tire.java(인터페이스)

package TestPackage;
public interface Tire {
	public void roll();
}

 

HankookTire.java(구현 클래스)

package TestPackage;
public class HankookTire implements Tire{
	@Override
	public void roll() {
		System.out.println("한국 타이어가 회전중");
	}
}

 

KumhoTire.java(구현 클래스)

package TestPackage;
public class KumhoTire implements Tire{
	@Override
	public void roll() {
		System.out.println("금호 타이어가 회전중");
	}
}

 

Car.java

package TestPackage;
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();
	}
}

 

Test.java

package TestPackage;
public class Test {
	public static void main(String[] args) {
		Car myCar = new Car();
		
		myCar.run();
		
		System.out.println("----------------------------------");
		
		myCar.frontLeftTire = new KumhoTire(); //타이어 인터페이스의 객체 교체
		myCar.frontRightTire = new KumhoTire();
		
		myCar.run();
	}
}
/*
한국 타이어가 회전중
한국 타이어가 회전중
한국 타이어가 회전중
한국 타이어가 회전중
----------------------------------
금호 타이어가 회전중
금호 타이어가 회전중
한국 타이어가 회전중
한국 타이어가 회전중
*/

Car의 run()메소드를 수정하지 않아도 다양한 roll()메소드의 실행결과를 얻을 수 있게 되었다. 이것이 필드의 다형성이다.

 

매개 변수의 다형성

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

매개값을 다양화하기 위해서 상속에서는 매개 변수를 부모 타입으로 선언하고 호출할 때에는 자식 객체를 대입한다.

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

 

Driver.java

package TestPackage;
public class Driver {
	public void drive(Vehicle vehicle)
	{
	vehicle.run();	
	}
}

 

Vehicle.java(인터페이스)

package TestPackage;
public interface Vehicle {
	public void run();
}

 

Bus.java(구현 클래스)

package TestPackage;
public class Bus implements Vehicle{
	@Override
	public void run() {
		System.out.println("버스 주행중");
	}
}

 

Taxi.java(구현 클래스)

package TestPackage;
public class Taxi implements Vehicle{
	@Override
	public void run() {
		System.out.println("택시가 주행중");
	}
}

 

Test.java

package TestPackage;
public class Test {
	public static void main(String[] args) {
		Driver driver = new Driver();
		
		Bus bus = new Bus();
		Taxi taxi = new Taxi();
		
		driver.drive(bus); //자동 타입 변환
		driver.drive(taxi); //자동 타입 변환
	}
}
/*
버스 주행중
택시가 주행중
*/

 

 

강제 타입 변환

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

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

 

Vehicle.java(인터페이스)

package TestPackage;
public interface Vehicle {
	public void run();
}

 

Bus.java(구현 클래스)

package TestPackage;
public class Bus implements Vehicle{
	@Override
	public void run() {
		System.out.println("버스 주행중");
	}
	public void checkFare() {
		System.out.println("승차요금 확인");
	}
}

 

Test.java

package TestPackage;
public class Test {
	public static void main(String[] args) {
		Vehicle vehicle = new Bus();
		
		vehicle.run();
		//vehicle.checkFare(); 컴파일 에러
		
		Bus bus = (Bus)vehicle; // 강제 타입 변환
		bus.run();
		bus.checkFare();
	}
}
/*
버스 주행중
버스 주행중
승차요금 확인
*/

 

 

객체 타입 확인

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

이러한 예외에 대처하기 위해서 instanceof 연산자를 이용하면 된다.

package TestPackage;
public interface Vehicle {
	public void run();
}
package TestPackage;
public class Bus implements Vehicle{
	@Override
	public void run() {
		System.out.println("버스 주행중");
	}
	public void checkFare() {
		System.out.println("승차요금 확인");
	}
}
package TestPackage;
public class Taxi implements Vehicle{
	@Override
	public void run() {
	System.out.println("택시 주행중");	
	}
}
package TestPackage;
public class Driver {
	public void drive(Vehicle vehicle) {
		if(vehicle instanceof Bus) { //vehicle 매개 변수가 참조하는 객체가 Bus 인지 확인
			Bus bus = (Bus) vehicle; //조건문이 참일경우 강제 타입변환
			bus.checkFare();
		}
		vehicle.run();
	}
}
package TestPackage;
public class Test {
	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{}

하위 인터페이스를 구현하는 클래스는 하위 인터페이스의 메소드뿐만 아니라 상위 인터페이스의 모든 추상 메소드에 대해 실체 메소드를 가지고 있어야 한다.

그렇기 때문에 구현 클래스로부터 객체를 생성한 후에 하위 및 상위 인터페이스 타입으로 변환이 가능하다.

하위 인터페이스로 타입 변환이 되면 상위 및 하위 인터페이스에 선언된 모든 메소드를 사용할 수 있으나, 상위 인터페이스로 타입 변환되면 상위 인터페이스에 선언된 메소드만 사용 가능하고 하위 인터페이스에 선언된 메소드는 사용할 수 없다

.

InterfaceC 인터페이스 변수는 methodA(), methodB(), methodC()를 모두 호출할 수 있지만, InterfaceA와 InterfaceB 변수는 각각 methodA(), methodB()만 호출할 수 있다.

package TestPackage;
public interface InterfaceA {
	public void methodA();
}
package TestPackage;
public interface InterfaceB {
	public void methodB();
}
package TestPackage;
public interface InterfaceC extends InterfaceA, InterfaceB {
	public void methodC();
}
package TestPackage;
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() 실행");
	}
}
package TestPackage;
public class Test {
	public static void main(String[] args) {
		ImplementationC impl = new ImplementationC();
		
		InterfaceA ia = impl; // methodA()만 호출 가능
		ia.methodA(); 
		System.out.println();
		
		InterfaceB ib = impl; // methodB()만 호출 가능
		ib.methodB();
		System.out.println();
		
		InterfaceC ic = impl; // methodA(), methodB(),methodC() 모두 호출 가능
		ic.methodA();
		ic.methodB();
		ic.methodC();
	}
}
/*
ImplementationC-methodA() 실행

ImplementationC-methodB() 실행

ImplementationC-methodA() 실행
ImplementationC-methodB() 실행
ImplementationC-methodC() 실행
*/

댓글