on
혼자 공부하는 자바 Chapter 08 인터페이스 | 개발 공부 | Java
혼자 공부하는 자바 Chapter 08 인터페이스 | 개발 공부 | Java
08-1 인터페이스
자바에서 인터페이스는 객체의 사용 방법을 정의한 타입이다.
인터페이스를 통해 가양한 객체를 동일한 사용 방법으로 이용할 수 있다.
인터페이스는 개발 코드와 객체가 서로 통신하는 접점 역할을 한다.
개발 코드가 인터페이스의 메소드를 호출하면 인터페이스는 객체의 메소드를 호출시키기 때문에 개발 코드는 객체의 내부 구조를 알 필요가 없고 인터페이스의 메소드만 알고 있으면 된다.
개발 코드가 직접 객체의 메소드를 호출하지 않고 중간에 인터페이스를 두는 이유는 개발 코드를 수정하지 않고 사용하는 객체를 변경할 수 있도록 하기 위해서이다.
인터페이스는 하나의 객체가 아니라 여러 객체들과 사용이 가능하므로 어떤 객체를 실행하느냐에 따라서 실행 내용과 리턴값이 다를 수 있다.
개발 코드 측면에서는 코드 변경없이 실행 내용과 리턴값을 다양화할 수 있다는 장점을 가지게 된다.
인터페이스 선언
인터페이스 선언은 class 키워드 대신에 interface 키워드를 사용한다.
클래스는 필드, 생성자, 메소드를 구성 멤버로 가지는데 반해, 인터페이스는 상수 필드와 추상 메소드만을 구성 멤버로 가진다.
인터페이스는 객체로 생성할 수 없기 때문에 생성자를 가질 수 없다.
상수 필드 선언
인터페이스는 객체 사용 방법을 정의한 것이므로 실행 시 데이터를 저장할 수 있는 인스턴스 또는 정적 필드를 선언할 수 없다.
상수 필드는 선언이 가능한데, 상수는 인터페이스에 고정된 값으로 실행 시에 데이터를 바꿀 수 없다.
인터페이스에 선언된 필드는 모두 public static final 의 특성을 갖는데, public static final 을 생략하더라도 컴파일 과정에서 자동으로 붙게 된다.
추상 메소드 선언
인터페이스를 통해 호출된 메소드는 최종적으로 객체에서 실행되기 때문에 인터페이스의 메소드는 실행 블록이 필요 없는 추상 메소드로 선언한다.
인터페이스에 선언된 추상 메소드는 모두 public abstract 의 특성을 갖기 때문에 public abstract 를 생략하더라도 컴파일 과정에서 자동으로 붙게 된다.
public interface RemoteControl { // public static final public int MAX_VOLUME = 10; public int MIN_VOLUME = 0; // public abstract public void turnOn(); public void turnOff(); public void setVolume(int volume); }
인터페이스 구현
개발 코드가 인터페이스 메소드를 호출하면 인터페이스는 객체의 메소드를 호출한다.
객체는 인터페이스에서 정의된 추상 메소드와 동일한 메소드 이름, 매개 타입, 리턴 타입을 가진 실체 메소드를 가지고 있어야 한다.
실체 메소드를 가지고 있는 객체를 인터페이스의 구현 객체라고 하고, 구현 객체를 생성하는 클래스를 구현 클래스라고 한다.
구현 클래스
구현 클래스는 보통의 클래스와 동일한데, 인터페이스 타입으로 사용할 수 있음을 알려주기 위해 클래스 선언부에 implements 키워드를 추가하고 인터페이스 이름을 명시해야 한다.
인터페이스에 선언된 추상 메소드의 실체 메소드를 선언해야 한다.
public class Television implements RemoteControl { private int volume; public void turnOn() { System.out.println("turnOn()"); } public void turnOff() { System.out.println("turnOff()"); } public void setVolume(int volume) { if (volume > RemoteControl.MAX_VOLUME) { this.volume = RemoteControl.MAX_VOLUME; } else if (volume < RemoteControl.MIN_VOLUME) { this.volume = RemoteControl.MIN_VOLUME; } else { this.volume = volume; } System.out.println("setVolume(" + volume + ")"); } }
public class Audio implements RemoteControl { private int volume; public void turnOn() { /* ... */ } public void turnOff() { /* ... */ } public void setVolume(int volume) { /* ... */ } }
public static void main(String[] args) { RemoteControl rc1 = new Television(); rc1.turnOn(); rc1.setVolume(5); rc1.turnOff(); RemoteControl rc2 = new Audio(); rc2.turnOn(); rc2.setVolume(5); rc2.turnOff(); }
다중 인터페이스 구현 클래스
객체는 다수의인터페이스 타입으로 사용할 수 있다.
다중 인터페이스를 구현할 경우, 구현 클래스는 모든 인터페이스의 추상 메소드에 대해 실체 메소드를 작성해야 한다.
public interface Searchable { void search(String url); }
public class SmartTelevision implements RemoteControl, Searchable { private int volume; public void turnOn() { /* ... */ } public void turnOff() { /* ... */ } public void setVolume(int volume) { /* ... */ } public void search(String url) { System.out.println("search(" + url + ")"); } }
인터페이스 사용
구현 객체가 인터페이스 타입에 대입되면 인터페이스에 선언된 추상 메소드를 개발 코드에서 사용할 수 있게 된다.
public class User { public void handle(RemoteControl rc) { rc.turnOn(); rc.setVolume(5); rc.turnOff(); } }
public static void main(String[] args) { User user = new User(); user.handle(new Television()); user.handle(new Audio()); }
08-2 타입 변환과 다형성
다형성을 구현하기 위해서는 메소드 재정의와 타입 변환이 필요하다.
인터페이스 역시 이 두 가지 기능이 제공되므로 상속돠 더불어 다형성을 구현하는 데 많이 사용된다.
상속은 같은 종류의 하위 클래스를 만드는 기술이고, 인터페이스는 사용 방법이 동일한 클래스를 만드는 기술이라는 개념상 차이가 있지만 둘 다 다형성을 구현하는 방법은 비슷하다.
프로그램 소스 코드는 변함이 없는데, 구현 객체를 교체함으로써 프로그램의 실행 결과가 다양해지는데 이것이 인터페이스의 다형성이다.
자동 타입 변환
구현 객체가 인터페이스 타입으로 변환되는 것은 자동 타입 변환에 해당한다.
자동 타입 변환은 프로그램 실행 도중에 자동적으로 타입 변환이 일어나는 것을 말한다.
인터페이스 구현 클래스를 상속해서 자식 클래스를 만들었다면 자식 객체 역시 인터페이스 타입으로 자동 타입 변환할 수 있다.
자동 타입 변환을 이용하면 필드의 타형성과 매개 변수의 다형성을 구현할 수 있다.
필드와 매개 변수의 타입을 인터페이스로 선언하면 다양한 구현 객체를 대입해서 실행 결과를 다양하게 만들 수 있다.
필드의 다형성
public interface Tire { public void roll(); }
public class HankookTire implements Tire { @Override public void roll() { System.out.println("HankookTire roll()"); } }
public class KeumhoTire implements Tire { @Override public void roll() { System.out.println("KeumhoTire roll()"); } }
public class Car { Tire frontLeftTire = new HankookTire(); Tire frontRightTire = new HankookTire(); Tire backLeftTire = new KeumhoTire(); Tire backRightTire = new KeumhoTire(); public void run() { frontLeftTire.roll(); frontRightTire.roll(); backLeftTire.roll(); backRightTire.roll(); } }
매개 변수의 다형성
자동 타입 변환은 필드의 값을 대입할 때에도 발생하지만, 주로 메소드를 호출할 때 많이 발생한다.
매개 변수를 인터페이스 타입으로 선언하고 호출할 때에는 구현 객체를 대입한다.
매개 변수의 타입이 인터페이스일 경우 어떠한 구현 객체도 매개값으로 사용할 수 있고, 어떤 구현 객체가 제공되느냐에 따라 메소드의 실행 결과가 다양해질 수 있는데, 이것이 인터페이스 매개 변수 다형성이다.
public interface Vehicle { public void run(); }
public class Bus implements Vehicle { @Override public void run() { Ststem.out.println("Vehicle run()"); } }
public class Taxi implements Vehicle { @Override public void run() { Ststem.out.println("Taxi run()"); } }
public class Driver { public void drive(Vehicle vehicle) { vehicle.run(); } }
public static void main(String[] args) { Driver driver = new Driver(); driver.drive(new Bus()); driver.drive(new Taxi()); }
강제 타입 변환
구현 객체가 인터페이스 타입으로 자동 타입 변환하면, 인터페이스에 선언된 메소드만 사용 가능하다는 제약 사항이 따른다.
경우에 따라서는 구현 클래스에 선언된 필드와 메소드를 사용해야 할 경우도 발생하는데, 이때 강제 타입 변환을 해서 다시 구현 클래스 타입으로 변환한 다음, 구현 클래스의 필드와 메소드를 사용할 수 있다.
public class Bus implements Vehicle { @Override public void run() { Ststem.out.println("Vehicle run()"); } public void checkFare() { Ststem.out.println("checkFare()"); } }
public static void main(String[] args) { Vehicle vehicle = new Bus(); vehicle.run(); // vehicle.checkFare(); Bus bus = (Bus) vehicle; bus.run(); bus.checkFare(); }
객체 타입 확인
강제 타입 변환은 구현 객체가 인터페이스 타입으로 변환되어 있는 상태에서 가능하다.
어떤 구현 객체가 변환되어 있는지 알 수 없는 상태에서 무작정 강제 타입 변환할 경우 ClassCastException이 발생할 수도 있다.
상속에서 객체 타입을 확인하기 위해 instanceof 연산자를 사용했는데, instanceof 연산자는 인터페이스 타입에서도 사용할 수 있다.
인터페이스 타입으로 자동 타입 변환된 매개값을 메소드 내에서 다시 구현 클래스 타입으로 강제 타입 변환해야 한다면 반드시 매개값이 어떤 객체인지 instanceof 연산자로 확인하고 안전하게 강제 타입 변환을 해야 한다.
public class Driver { public void drive(Vehicle vehicle) { if (vehicle instanceof Bus) { Bus bus = (Bus) vehicle; bus.checkFare(); } vehicle.run(); } }
인터페이스 상속
인터페이스도 다른 인터페이스를 상속할 수 있다.
인터페이스는 클래스와는 달리 다중 상속을 허용한다.
하위 인터페이스를 구현하는 클래스는 하위 인터페이스의 메소드뿐만 아니라 상위 인터페이스의 모든 추상 메소드에 대한 실체 메소드를 가지고 있어야 한다.
하위 인터페이스로 타입 변환이 되면 상위 및 하위 인터페이스에 선언된 모든 메소드를 사용할 수 있으나, 상위 인터페이스로 타입 변환되면 상위 인터페이스에 선언된 메소드만 사용 가능하고 하위 인터페이스에 선언된 메소드는 사용할 수 없다.
public interface InterfaceA { public void methodA(); }
public interface InterfaceB { public void methodB(); }
public interface InterfaceC extends InterfaceA, InterfaceB { public void methodC(); }
public class Implementation implements InterfaceC { @Override public void methodA() { /* ... */ } @Override public void methodB() { /* ... */ } @Override public void methodC() { /* ... */ } }
public static void main(String[] args) { Implementation impl = new Implementation(); InterfaceA ia = impl; ia.methodA(); // ia.methodB(); // ia.methodC(); InterfaceB ib = impl; ib.methodB(); // ib.methodA(); // ib.methodC(); InterfaceC ic = impl; ic.methodA(); ic.methodB(); ic.methodC(); }
참조
from http://dhpark.tistory.com/26 by ccl(A) rewrite - 2021-12-09 14:27:28