다형성을 구현하기 위해서는 메소드 재정의와 타입 변환이 필요하다. 인터페이스 역시 이 두가지 기능이 제공되므로 상속과 더불어 다형성을 구현하는데 많이 사용된다.
상속은 같은 종류의 하위 클래스를 만드는 기술이고,
인터페이스는 사용방법이 동일한 클래스를 만드는 기술 이다.
개념상 차이는 있지만 상속과 인터페이스 둘 다 다형성을 구현하는 방법은 비슷하다.
인터페이스의 다형성
인터페이스 사용 방법은 동일하지만 구현 객체를 교체하여 프로그램 실행 결과를 다양하게 할 수 있다.
자동 타입 변환 (promotion)
구현 객체가 인터페이스 타입으로 변환되는 것이다.
구현 객체와 자식 객체는 인터페이스 타입으로 자동 타입 변환이 된다.
인터페이스 변수 = 구현객체;
//구현객체가 인터페이스 타입으로 변환
필드의 다형성
다양항 메소드의 실행 결과를 얻을 수 있다.
예시1) 필드의 다형성
인터페이스
public interface Tire {
public void roll();
}
구현 클래스
public class HankookTire implements Tire {
// Tire 인터페이스 구현
@Override
public void roll() {
System.out.println("한국 타이어가 굴러간다.");
}
}
구현 클래스
public class KumhoTire implements Tire{
// 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 CarEx {
public static void main(String[] args) {
Car myCar = new Car();
myCar.run();
myCar.frontLeftTire = new KumhoTire();
myCar.frontRightTire = new KumhoTire();
myCar.run();
}
}
매개변수의 다형성
매개 변수를 인터페이스 타입으로 선언하고 호출할 때에 구현 객체를 대입한다.
public interface Vehicle{
public void run();
}
public class Driver{
publici void drive(Vehicle vehicle){ //구현 객체
vehicle.run(); //구현 객체의 run()메소드 실행
}
}
예시2) 필드의 다형성
매개 변수의 인터페이스화
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 DriverEx {
public static void main(String[] args) {
Driver driver = new Driver();
Bus bus = new Bus();
Taxi taxi = new Taxi();
driver.drive(bus);
driver.drive(taxi);
System.out.println("-----------");
// 위에꺼 간결하게 표현
driver.drive(new Bus());
driver.drive(new Taxi());
}
}
강제 타입 변환(casting)
구현 객체가 인터페이스 타입으로 자동 타입 변호나하면, 인터페이스에 선언된 메소드만 사용 가능하다는 제약이 있다.
하지만, 경우에 따라서 구현 클래스에 선언된 필드와 메소드를 사용해야 할 경우도 발생한다. 이때 강제 타입 변환을 해서 다시 구현 클래스 타입으로 변환한 다음, 구현 클래스의 필드와 메소드를 사용 할 수 있다.
예시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 VehicleEx {
public static void main(String[] args) {
Vehicle vehicle = new Bus();
vehicle.run();
Bus bus = (Bus) vehicle; // 강제 타입 변환
bus.run();
bus.checkFare(); // Bus클래스에는 checkFare()가 있음
}
}
객체 타입 확인
어떤 구현 객체가 변환 되어 있는지 알 수 없는 상태에서 무작정 강제 타입 변환하는 경우 ClassCastException이 발생 할 수 있다.
상속에서 객체 타입을 확인하기 위해 instanceof 연산자를 이용했었다. instanceof연산자는 인터페이스 타입에서도 사용할 수 있다.
if(vehicle instanceof Bus){
Bus bus = (Bus) vehicle;
}
예시4) 객체 타입 확인
인터페이스
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 Taxi implements Vehicle{
@Override
public void run() {
System.out.println("택시가 달린다.");
}
}
객체 타입 확인
public class Driver {
public void drive(Vehicle vehicle) {// Bus객체, Taxi객체
if (vehicle instanceof Bus) { // vehicle 매개 변수가 참조하는 객체가 Bus인지 조사
Bus bus = (Bus) vehicle; // BUs객체일 경우 안전하게 강제 타입 변환
bus.checkFare(); // Bus타입으로 강제타입변환하는 이유
}
vehicle.run();
}
}
객체 타입 확인
public class DriverEx {
public static void main(String[] args) {
Driver driver = new Driver();
driver.drive(new Bus());
driver.drive(new Taxi());
}
}
인터페이스 상속
인터페이스는 클래스와 달리 다중 상속을 허용하며, 인터페이스도 다른 인터페이스를 상속할 수 있다.
public interface 하위인터페이스 extends 상위인터페이스1, 상위인터페이스2{ ... }
예시5) 인터페이스 상속
상위 인터페이스
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 {
@Override
public void methodA() {
System.out.println("ImplementationC-methodA() 실행");
}
@Override
public void methodB() {
System.out.println("ImplementationC-methodB() 실행");
}
@Override
public void methodC() {
System.out.println("ImplementationC-methodC() 실행");
}
}
호출 가능 메소드
public class Ex {
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();
}
}
※용어 정리
-
자동 타입 변환: 구현 객체가 인터페이스 변수에 대입되는 것
-
다형성: 상속은 같은 종류의 하위 클래스를 만드는 기술이고, 인터페이스는 사용 방법이 동일한 클래스를 만드는 기술이라는 개념상의 차이는 있지만, 둘다 다형성을 구현하는 방법은 비슷하다. 모두 재정의와 타입 변환 기능을 제공하기 때문이다.
-
강제 타입 변환: 인터페이스에 대입된 구현 객체를 다시 원래 구현 클래스 타입으로 변환하는 것
-
instanceof: 강제 타입 변환을 하기 전에 변환이 가능한지 조사할 때 사용. 상속에서는 자식 클래스 타입인지, 인터페이스에서는 구현 클래스 타입인지를 확인할 때 사용
-
인터페이스 상속: 인터페이스는 다중 상속을 허용한다. extends 키워드 뒤에 상위 인터페이스가 올 수 있다.
본 내용은 #혼자공부하는자바 책을 참고해 공부하려 작성했습니다.
'Work & Study > JAVA (& 혼공자Java)' 카테고리의 다른 글
자바(Java) - 중첩 클래스의 접근 제한 (0) | 2020.11.23 |
---|---|
자바(Java) - 중첩 클래스와 중첩 인터페이스 (0) | 2020.11.23 |
자바(Java) - 인터페이스 (0) | 2020.11.16 |
자바(Java) - 추상 클래스 (0) | 2020.11.09 |
자바(Java) - 강제 타입 변환 (0) | 2020.11.05 |