- 객체의 사용 방법을 정의한 타입
- 객체의 교환성을 높여주어 다형성을 구현하는 매우 중요한 역할을 한다
[출처] : 이것이 자바다 - 신용권의 Java 프로그래밍 정복 1권 p.344
- 위 그림처럼, 개발 코드가 인터페이스의 메소드를 호출하면 → 인터페이스는 객체의 메소드를 호출한다
- 이렇게 되면, 개발 코드는 객체의 내부 구조를 알 필요가 없고, 인터페이스의 메소드만 알면 된다는 장점을 가진다
- 개발 코드를 수정하지 않고, 사용하는 객체를 변경할 수 있도록 하기 위해서 인터페이스를 쓴다
- 인터페이스는 하나의 객체가 아니라, 여러 객체들과 사용이 가능하다
- 그래서, 어떤 객체를 사용하느냐에 따라서 실행 내용과 리턴값이 다를 수 있다
- 개발 코드 측면에서는 코드 변경 없이 실행 내용과 리턴값을 다양화 할 수 있다는 장점을 가진다
- 그래서, 어떤 객체를 사용하느냐에 따라서 실행 내용과 리턴값이 다를 수 있다
[출처] : 이것이 자바다 - 신용권의 Java 프로그래밍 정복 1권 p.344
인터페이스는 ~.java 형태의 소스 파일로 작성되고, 컴파일러 (javac.exe)를 통해 ~.class 형태로 컴파일 되므로
물리적 형태는 클래스와 동일하다. 다만, 소스 작성 시, 선언 방식이 다르다
-
interface키워드를 사용하여 선언한다[public] interface 인터페이스명 { ... }
-
인터페이스 이름은 클래스 이름 작성 방법과 동일하다
영어 대소문자를 구분하며, 첫 문자를 대문자로작성하는 것이 관례
-
인터페이스는 상수와 메소드만을 구성 멤버로 가진다
클래스는 필드, 생성자, 메소드를 구성 멤버로 가진다
-
인터페이스는 객체로 생성할 수 없기 때문에 생성자를 가질 수 없다
-
자바 7이전까지는 인터페이스의 메소드는 실행 블록이 없는 추상 메소드로만 선언이 가능했지만,
-
자바 8부터는 디폴트 메소드와 정적 메소드도 선언이 가능하다
interface 인터페이스명 {
// 상수
타입 상수명 = 값;
// 추상 메소드
타입 메소드명(매개변수, ...);
// 디폴트 메소드
default 타입 메소드명(매개변수, ...) {...}
// 정적 메소드
static 타입 메소드명(매개변수) {...}
}- 인터페이스는 객체 사용 설명서 이므로 런타임 시 데이터를 저장할 수 있는 필드를 선언할 수 없다
- 그러나, 상수 필드는 선언이 가능하다
- 상수는 인터페이스에 고정된 값으로, 런타임 시에 데이터를 바꿀 수 없기 때문이다
- 상수 선언하려면, 반드시 선언 할 때 초기값을 대입해야 함
- 객체가 가지고 있는 메소드를 설명한 것으로,
- 호출 시, 어떤 매개값이 필요하고, 리턴 타입이 무엇인지만 알려준다
- 실제 실행부는 객체 (구현 객체)가 가지고 있다
- 자바 8부터 작성 가능
- 인터페이스에 선언되지만 사실은 구현 객체가 가지고 있는 인스턴스 메소드
- 자바 8부터 작성 가능
- 디폴트 메소드와는 달리 객체가 없더오 인터페이스만으로 호출이 가능
- 자바 9부터 작성 가능
- 자바 9부터 작성 가능
- 먼저 자바 7 기준으로 설명하고, 이후의 목차에서 자바 8 & 자바 9에서 도입된 인터페이스의 구성 멤버에 대해 알아보자
-
인터페이스는 데이터를 저장할 수 없기 때문에 데이터를 저장할 인스턴스 또는 정적 필드를 선언할 수 없음
-
대신 상수 필드만 선언 가능하다
[ public static final ] 타입 상수명 = 값;
public, static, final을 생략해도 컴파일 과정에서 자동으로 붙는다
-
인터페이스 상수는 static {} 블록으로 초기화 할 수 없기 때문에, 반드시 선언과 동시에 초기값을 지정해 주어야 함
/* 상수 필드 선언 */
public interface RemoteControl {
public int MAX_VOLUME = 10;
public int MIN_VOLUME = 0;
}-
인터페이스를 통해 호출된 메소드는 최종적으로 객체에서 실행된다
-
따라서, 인터페이스의 메소드는 → 어차피 객체에서 실행되니까 실행 블록이 필요없는 추상 메소드로 선언한다
추상 메소드 (Abstract Method)
- 리턴 타입, 메소드명, 매개변수 만 기술되고, 중괄호 {} 를 붙이지 않는 메소드
-
인터페이스에 선언된 추상 메소드는 모두
public abstract의 특성을 가짐public abstract를 생략하더라도 자동으로 컴파일 과정에서 붙음
-
[출처] : 이것이 자바다 - 신용권의 Java 프로그래밍 정복 1권 p.347
/* 추상 메소드 선언 */
/* RemoteControl 인터페이스에서 turnOn(), turnOff(), setVolume() 추상 메소드를 선언*/
public interface RemoteControl {
// 상수
public int MAX_VOLUME = 10;
public int MIN_VOLUME = 0;
// 추상 메소드 -> 메소드 선언부만 작성함
public void turnOn();
public void turnOff();
public void setVolume(int volume);
}-
개발 코드가 인터페이스 메소드를 호출하면 인터페이스는 객체의 메소드를 호출 한다
-
객체는 인터페이스에서 정의된 추상 메소드와 동일한 메소드 이름, 매개 타입, 리턴 타입을 가진 실체 메소드를 가지고 있어야 함
- 이런 객체를 인터페이스의 구현(implement) 객체라고하고, 구현 객체를 생성하는 클래스를 구현 클래스라고 한다
[출처] : 이것이 자바다 - 신용권의 Java 프로그래밍 정복 1권 p.344
- 실체 메소드를 가진 객체
- 인터페이스의 구현 (implement) 객체
- 구현 객체를 생성하는 클래스
- 구현 클래스
-
보통의 클래스와 동일하지만, 인터페이스 타입으로 사용할 수 있음을 알려주기 위해
→ 클래스 선언부에 implements 키워드 추가 & 인터페이스명을 명시
public class 구현클래스명 implements 인터페이스명 { // 인터페이스에 선언된 추상 메소드의 실체 메소드 선언 }
- 아래 클래스는
RemoteControl인터페이스에 선언한 추상 메소드들에 대한 실체 메소드를 가져야 함
/* 구현 클래스 */
// 구현 클래스 - Television
public class Television implements RemoteControl {
// 필드
private int volume;
// turnOn() 추상 메소드의 실체 메소드
public void turnOn() {
System.out.println("TV를 켭니다.");
}
// turnOff() 추상 메소드의 실체 메소드
public void turnOff() {
System.ouut.println("TV를 끕니다.");
}
// setVolume() 추상 메소드의 실체 메소드
public void setVolume(int volume) {
if (volume > RemoteControl.MAX_VOLUME) { // 인터페이스 상수를 이용해서, volume 필드의 값을 제한
this.volume = RemoteControl.MAX_VOLUME;
} else if (volume < RemoteControl.MIN_VOLUME) {
this.volume = RemoteControl.MIN_VOLUME;
} else {
this.volume = volume;
}
System.out.println("현재 TV 볼륨 : " + volume);
}
}####2. 이전에 선언했던 RemoteControl 인터페이스를 구현하는 Television 클래스 예제
- 아래 클래스는
RemoteControl인터페이스에 선언한 추상 메소드들에 대한 실체 메소드를 가져야 함
// 구현 클래스 - Audio
public class Audio implements RemoteControl {
// 필드
private int volume;
// turnOn() 추상 메소드의 실체 메소드
public void turnOn() {
System.out.println("Audio를 켭니다.");
}
// turnOff() 추상 메소드의 실체 메소드
public void turnOff() {
System.out.println("Audio를 끕니다.");
}
// setVolume() 추상 메소드의 실체 메소드
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("현재 Audio 볼륨 : " + volume);
}
}-
인터페이스의 모든 메소드는 기본적으로
public접근 제한을 가짐- 따라서, 구현 클래스의 실체 메소드들은
public보다 낮은 접근 제한자를 가질 수 없음public생략 시Cannot reduce the visibility of the inherited method: 컴파일 에러 발생
- 따라서, 구현 클래스의 실체 메소드들은
-
만약, 인터페이스에 선언된 추상 메소드에 대응하는 실체 메소드를 구현 클래스가 작성하지 않으면
- 구현 클래스는 자동적으로 추상 클래스가 됨
-
abstract키워드 필수// setVolume() 실체 메소드가 없음 (일부만 구현됨) -> Television 클래스는 자동으로 추상 클래스가 됨 public abstract class Television implements RemoteControl { public void turnOn() { ... } public void turnOff() { ... } }
-
구현 클래스가 작성되면
new연산자로 객체를 생성할 수 있음 -
그러나, 아래와 같이
Television객체를 생성하고,Television변수에 대입한다고 인터페이스를 사용하는 것이 아님Television tv = new Television(); // 인터페이스의 사용 X
-
인터페이스로 구현 객체를 사용하려면 인터페이스 변수를 선언하고 구현 객체를 대입해야한다. 인터페이스 레퍼런스를 통해 구현체를 사용하는 방법은 이후 목차에서 알아보자
방금 알아봤던 구현 클래스를 만들어 사용하는 것이 일반적이고, 클래스를 재사용 할 수 있기 때문에 편리하지만,
일회성의 구현 객체를 만들기 위해 소스 파일을 만들고 클래스를 선언하는 것은 비효율적이다.
-
그래서, 자바에서는 소스 파일을 만들지 않고도 구현 객체를 만들 수 있는 방법 제공한다
→ 익명 구현 객체
ex) UI 프로그래밍에서 이벤트 처리를 위해, 임시 작업 스레드를 만들기 위해 , 익명 구현 객체를 활용
ex2) 자바 8의 람다식 → 인터페이스의 익명 구현 객체를 만든다
- 하나의 실행문이므로 끝에는 세미콜론을 반드시 붙여야 함
인터페이스 변수 = new 인터페이스() {
// 인터페이스에 선언된 추상 메소드의 실체 메소드 선언 -> 작성 안하면 컴파일 에러
// 필드와 메소드를 선언할 수 있지만, 익명 객체 안에서만 사용 가능, 인터페이스 변수로 접근 불가
};new연산자 뒤에는 클래스이름이 와야 하는데, 이름이 없는 것이 특징이다- 중괄호
{}에는 인터페이스에 선언된 모든 추상 메소드들의 실체 메소드를 작성해야 한다. 그렇지 않으면 컴파일 에러가 발생한다 - 또한, 필드와 메소드를 선언할 수 있지만, 익명 객체 안에서만 사용할 수 있고 인터페이스 변수로 접근할 수 없다는 특징을 갖는다
/* 익명 구현 객체 */
// 익명 구현 클래스
public class RetmoteControllerExample {
public static void main(String[] args) {
RemoteControl rc = new RemoteControl() {
public void turnOn() {
/* 실행문 */
}
public void turnOff() {
/* 실행문 */
}
public void setVolume(int volume) {
/* 실행문 */
}
};
}
}모든 객체는 클래스로부터 생성되는데, 익명 구현 객체도 예외는 아님
RemoteContorollerExample.java를 컴파일하면 자바 컴파일러에 의해 자동으로 다음과 같은 클래스 파일이 만들어짐
RemoteControllerExample$1.class
- 두번째 익명 구현 객체를 만들었다면 → 클래스 파일명은 RemoteControllerExample$2.class
-
객체는 아래 그림 처럼다수의 인터페이스 타입으로 사용할 수 있다
-
**다중 인터페이스 구현 시, 구현 클래스는 모든 인터페이스의 추상 메소드에 대해 실체 메소드를 작성해야 함 **
-
(하나라도 없으면 추상 클래스로 선언해야 함)
[출처] : 이것이 자바다 - 신용권의 Java 프로그래밍 정복 1권 p.356
- 인터페이스 A와 인터페이스 B가 객체의 메소드를 호출할 수 있으려면
- 객체는 이 두 인터페이스를 모두 구현해야 함
public class 구현클래스명 implements 인터페이스A, 인터페이스B {
// 인터페이스 A에 선언된 추상 메소드의 실체 메소드 선언
// 인터페이스 B에 선언된 추상 메소드의 실체 메소드 선언
}Searchable인터페이스
// 인터페이스
public interface Searchable {
void search(String url); // 추상 메소드
}RemoteControl,Searchable인터페이스를 구현한SmartTelevision클래스
// 다중 인터페이스 구현 클래스
public class SmartTelevision implements RemoteControl, Searchable {
private int volume;
// RemoteControl의 추상 메소드에 대한 실체 메소드
public void turnOn() {
System.out.println("SmartTV를 켭니다.");
}
public void turnOff() {
System.out.println("SmartTV를 끕니다.");
}
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("현재 SmartTV 볼륨 : " + volume);
}
// Searchable 의 추상 메소드에 대한 실체 메소드
public void search(String url) {
System.out.println(url + "을 검색합니다.");
}
}인터페이스 변수;
변수 = 구현 객체;인터페이스 변수 = 구현 객체;-
인터페이스로 구현 객체를 사용하려면 → 인터페이스 변수를 선언하고 구현 객체를 대입해야 한다
-
인터페이스 변수는 참조타입이므로, 구현 객체가 대입될 경우, 구현 객체의 번지를 저장
RemoteControl rc;
rc = new Television();
rc = new Audio();RemoteControl인터페이스로 구현 객체인Television과Audio를 사용하려면,RemoteControl타입 변수rc를 선언하고 구현 객체를 대입해야 한다
- 구현 객체가 인터페이스 타입에 대입되면 → 인터페이스에 선언된 추상 메소드를 개발 코드에서 호출할 수 있다
- 코드에서
RemoteControl의 변수rc로turnOn()을 호출하면 구현 객체의turnOn()이 실행된다
RemoteControl rc = new Television();
rc.turnOn(); // Television의 turnOn() 실행
rc.turnOff(); // Television의 turnOff() 실행-
추상 메소드 사용 예제
/* 인터페이스 사용 */ public class RemoteControlExample { public static void main(String[] args) { RemoteControl rc = null; // 인터페이스 변수 선언 rc = new Television(); // Television 객체를 인터페이스 타입에 대입 rc.turnOn(); // 인터페이스에 선언된 추상 메소드를 개발코드에서 turnOn(), turnOff() 호출 rc.turnOff(); // 결국 구현 객체의 메소드가 자동 실행됨 rc = new Audio(); // Audio 객체를 인터페이스 타입에 대입 rc.turnOn(); // 인터페이스에 선언된 추상 메소드를 개발 코드에서 turnOn(), turnOff() 호출 rc.turnOff(); } } /*Result TV를 켭니다. TV를 끕니다. Audio를 켭니다. Audio를 끕니다. */
-
디폴트 메소드와 정적 메소드 사용은 이후 목차에서 살펴보자
클래스는 다중 상속 불가능
extends키워드를 이용하여 상속할 인터페이스를 나열한다
public interface 하위인터페이스 extends 상위인터페이스1, 상위인터페이스2 { ... }-
하위 인터페이스를 구현하는 클래스는 하위 인터페이스의 메소드 뿐만 아니라
- 상위 인터페이스의 모든 추상 메소드에 대한 실체 메소드까지 가지고 있어야 한다
- 이렇게 모든 실체 메소드를 가지고 있어야, 구현 클래스로부터 객체를 생성하고 나서, 하위 및 상위 인터페이스 타입으로 변환 가능 하다
하위인터페이스 변수 = new 구현클래스(...); 상위인터페이스1 변수 = new 구현클래스(...); 상위인터페이스2 변수 = new 구현클래스(...);
- 하위 인터페이스로 타입 변환이 되면
- 상, 하위 인터페이스에 선언된 모든 메소드를 사용할 수 있다
- 상위 인터페이스로 타입 변환되면
- 상위 인터페이스에 선언된 메소드만 사용 가능하다
- 하위 인터페이스에 선언된 메소드는 사용 불가하다
[출처] : 이것이 자바다 - 신용권의 Java 프로그래밍 정복 1권 p.377
-
InterfaceC인터페이스 변수는methodA(),methodB(),methodC()를 모두 호출할 수 있다 -
InterfaceA와InterfaceB변수는 각각methodA()와methodB()만을 호출할 수 있다
InterfaceA인터페이스 (부모 인터페이스)
// 부모 인터페이스
public interface InterfaceA {
public void methodA();
}InterfaceB인터페이스 (부모 인터페이스)
// 부모 인터페이스
public interface InterfaceB {
puublic void methodB();
}InterfaceC인터페이스 (하위 인터페이스)
// 하위 인터페이스
public interface InterfaceC extends interfaceA, interfaceB {
public void methodC();
}- 하위 인터페이스 구현 클래스
ImplementationC
// 하위 인터페이스 구현
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();
System.out.println(); // interfaceA 변수는 methodA()만 호출 가능
InterfaceB ib = impl;
ib.methodB();
System.out.println();
// interfaceC 변수는 methodA(), methodB(), methodC() 모두 호출 가능
InterfaceC ic = impl;
ic.methodA();
ic.methodB();
ic.methodC();
}
}- 형태는 클래스의 인스턴스 메소드와 동일
- 인터페이스에 선언되지만 사실은 객체 (구현 객체)가 가지고 있는 인스턴스 메소드
- 자바 8에서 디폴트 메소드를 허용한 이유는 기존 인터페이스를 확장해서 새로운 기능을 추가하기 위함
-
클래스의 인스턴스 메소드와 형태는 동일, default 키워드가 리턴 타입 앞에 붙음
-
public의 특성을 가짐 (public키워드 생략해도 컴파일 과정에서 자동으로 붙음)[public] default 리턴타입 메소드명(매개변수, ...) { ... }
/* 디폴트 메소드 선언 */
/* RemoteControl 인터페이스에서 무음 처리 기능을 제공하는 setMute() 디폴트 메소드를 선언 */
public interface RemoteControl {
...
// 디폴트 메소드
// 무음 처리 기능을 제공하는 setMute() 메소드 (실행 내용까지 작성)
default void setMute(boolean mute) {
if (mute) {
System.out.println("무음 처리합니다.");
} else {
System.out.println("무음 해제합니다.");
}
}
}-
디폴트 메소드는 인터페이스에 선언되지만, 인터페이스에서 바로 사용할 수 없음
-
디폴트 메소드는 추상 메소드가 아닌 인스턴스 메소드 이다
-
따라서, 구현 객체가 있어야 사용 가능하다
RemoteControl인터페이스는setMute()라는 디폴트 메소드를 가지고 있지만,- 아직
RemoteControl의 구현객체가 없어서 호출할 수 없음
RemoteControl.setMute(true); // 호출 불가 - 호출 가능하게 하려면,
RemoteControl의 구현 객체가 필요하다
RemoteControl rc = new Television(); // Television 객체를 인터페이스 변수에 대입하고 나서, setMute()를 호출할 수 있음
rc.setMute(true);-
비록,
setMute()가Television클래스에 선언되지는 않았지만 ,Television객체가 없으면setMute()도 호출할 수 없다
-
어떤 구현 객체는 디폴트 메소드의 내용이 맞지 않아, 수정이 필요할 수도 있다
-
구현 클래스를 작성할 때 디폴트 메소드를 재정의(오버라이딩)해서 자신에게 맞게 수정하면
- 디폴트 메소드가 호출될 때 자신을 재정의한 메소드가 호출된다
Audio클래스에서 default MethodsetMute()오버라이딩
/* 디폴트 메소드를 사용한 구현 클래스 */
public class Audio implements RemoteControl {
// 필드
private int volume;
private boolean mute;
...
// 디폴트 메소드 재정의
@Override
public void setMute(boolean mute) {
this.mute = mute;
if (mute) {
System.out.println("Audio 무음 처리합니다");
} else {
System.out.println("Audio 무음 해제합니다");
}
}
}- default method 사용 예시 -
RemoteConrolExample클래스
/* 디폴트 메소드 사용 예제 */
public class RemoteControlExample {
public static void main(String[] args) {
RemoteControl rc = null;
rc = new Television();
rc.turnOn();
rc.setMute(true);
rc = new Audio();
rc.turnOn();
rc.setMute(true);
}
}
/* Result
TV를 켭니다.
무음 처리합니다.
Audio를 켭니다.
Audio 무음 처리합니다.
*/- default method는 인터페이스에 선언된 인스턴스 메소드이기 때문에 구현 객체가 있어야 사용할 수 있다. 선언은 인터페이스에서 하고, 사용은 구현 객체를 통해 한다는 것이 낯설 수 있다
- default method는 모든 구현 객체에서 공유하는 기본 메소드처럼 보이지만, 인터페이스에서 default method가 필요한 이유가 있다
- 기존 인터페이스의 이름과 추상 메소드의 변경 없이 디폴트 메소드만 추가할 수 있기 때문에
- 이전에 개발한 구현 클래스를 그대로 사용할 수 있으면서 새롭게 개발하는 클래스는 디폴트 메소드를 활용 가능
[출처] : 이것이 자바다 - 신용권의 Java 프로그래밍 정복 1권 p.380
-
기존에
MyInterface인터페이스 -
이를 구현한
MyClassA클래스
가 있다
-
**
MyInterface**에 추상 메소드를 추가했다-
그런데,
-
MyClassA에는MyInterface에 추가된 추상 메소드에 대한 실체 메소드가 없어서 문제 발생!!!! -
결국
MyInterface에 추상 메소드를 추가할 수 없게 됨
-
-
MyInterface에 디폴트 메소드 선언 (MyClassA를 고치기 어려워서 고안)- default method는 추상 메소드가 아니므로, 구현 클래스에서 실체 메소드를 작성할 필요가 없음!
MyClassA는 별 문제 없이 계속 사용 가능
####반면, 수정된 MyInterface 를 구현한 새로운 클래스인 MyClassB 는 method1() 을 반드시 구현 필요
-
MyClassB는method1()의 내용은 반드시 채워야 하지만,method2()는 다르다. -
default method 인
method2()는 인터페이스에 정의된 것을 사용해도 되고, 필요에 따라 재정의하여 사용할 수도 있음
// 기존 인터페이스
public interface MyInterface {
public void method1();
}// 기존 구현 클래스
public class MyClassA implements MyInterface {
@Override
public void method1() {
System.out.println("MyClassA-method1() 실행");
}
}MyInterface에 디폴트 메소드method2()를 추가
// 수정 인터페이스
public interface MyInterface {
public void method1();
public default void method2() {
System.out.println("MyInterface-method2 실행");
}
}-
인터페이스를 수정하더라도
MyClassA는 컴파일 에러가 발생하지 않음 -
수정된 인터페이스를 구현한 새로운 구현 클래스
MyClassB
// 수정된 인터페이스를 구현한 인터페이스
public class MyClassB implements MyInterface {
@Override
public void method1() {
System.out.println("MyClassB-method1() 실행");
}
// 디폴트 메소드 재정의
@Override
public void method2() {
System.out.println("MyClassB-method2() 실행");
}
}-
Default Method 사용 예제
// 디폴트 메소드 사용 public class DefaultMethodExample { public static void main(String[] args) { MyInterface mi1 = new MyClassA(); mi1.method1(); mi1.method2(); MyInterface mi2 = new MyClassB(); mi2.method1(); mi2.method2(); } } /*Result * MyClassA-method1() 실행 * MyInterface-method2() 실행 * MyClassB-method1() 실행 * MyClassB-method2() 실행 */
- 부모 인터페이스에 default method가 정의되어 있는 경우, 자식 인터페이스에서 디폴트 메소드를 활용하는 방법은 아래와 같다
- 디폴트 메소드를 단순히 상속만 받음
- 디폴트 매소드를 재정의(Override)해서 실행 내용을 변경
- 디폴트 메소드를 추상 메소드로 재선언
- 추상 메소드와 디폴트 메소드가 선언된
ParentInterface
public interface ParentInterface {
public void method1();
public default void method2() { // 실행문 }
}-
ChildInterface1은ParentInterface를 상속하고, 자신의 추상 메소드인method3()을 선언public interface ChildInterface1 extends ParentInterface { public void method3(); }
-
ChildInterface1을 구현하는 클래스는method1()과method3()의 실체 메소드를 가지고 있어야 함 -
ParentInterface의method2()는 그냥 호출 가능ChildInterface1 ci1 = new ChildInterface() { @Override public void method1() { ... } @Override public void method3() { ... } }; ci1.method1(); ci1.method2(); // ParentInterface 의 method2() 호출 ci1.method3();
-
ChildInterface2는ParentInterface를 상속하고,method2()를 재정의 & 자신의 추상 메소드인method3()을 선언public interface ChildInterface2 extends ParentInterface { @Override public default void method2() { ... } // 재정의 public void method3(); }
-
ChildInterface2를 구현하는 클래스는method1()과method3()의 실체 메소드를 가지고 있어햐 함 -
ChildInterface2에서 재정의한method2()를 호출 가능ChildInterface2 ci2 = new ChildInterface2() { @Override public void method1() { ... } @Override public void method3() { ... } }; ci2.method1(); ci2.method2(); // ChildInterface2의 method2() 호출 ci2.method3();
-
ChildInterface3는ParentInterface를 상속하고ParentInterface의 디폴트 메소드인method2()를 추상 메소드로 재선언 & 자신의 추상 메소드인method3()을 선언public interface ChildInterface3 extends ParentInterface { @Override public void method2(); // 추상 메소드로 재선언 public void method3(); }
-
ChildInterface3를 구현하는 클래스는method1()과method2(),method3()의 실체 메소드를 모두 가져야 함ChildInterface3 ci3 = new ChildInterface3() { @Override public void method1() { ... } @Override public void method2() { ... } @Override public void method3() { ... } }; ci3.method1(); ci3.method2(); // ChildInterface3의 method2() 호출 ci3.method3();
- 자바 8에서 추가된 인터페이스의 새로운 멤버
- default method와는 달리 객체가 없어도 인터페이스만으로 호출 할 수 있다
-
형태는 클래스의 정적 메소드와 동일하다
-
정적 메소드는
public특성을 가짐 (public키워드 생략해도 컴파일 과정에서 자동으로 붙는다)[public] static 리턴타입 메소드명(매개변수, ...) { ... }
RemoteControl인터페이스에changeBattery()static method 선언
/* RemoteControl 인터페이스에서, 배터리 교환하는 기능을 가진 changeBattery() 정적 메소드를 선언 */
public interface RemoteControl {
...
// 정적 메소드
// 배터리를 교환하는 기능을 가진 changeBattery() 정적 메소드
static void changeBattery() {
System.out.println("건전지를 교환합니다.");
}
...
}public class RemoteControlExample {
public static void main(String[] args) {
RemoteControl.changeBattery();
}
}- 자바 9에서 추가된 인터페이스의 새로운 멤버
privatemethod와private staticmethod 를 인터페이스에 추가 할 수 있음- 중복된 코드를 작성할 필요가 없어서, 코드 재사용성이 증가한다
- private method를 이용하면 해당 메소드를 인터페이스를 구현하는 클래스에 노출하지 않아도 된다 => 캡슐화
-
private접근 지시자를 반드시 사용해야 함 -
private과abstract키워드를 동시에 사용할 수 없다-
동시에 사용 시 컴파일 에러
- private methods
- private method는 해당 클래스에서만 사용 가능하다, 결국 하위 클래스가 상속할 수 없고, 이 메소드를 재정의(오버라이딩) 할 수 없다는 의미이기 때문에 추상 메소드가 될 수없다
- 즉, 구현이 되어 있어야만 한다
- abstract methods
- 추상 메소드는 구현부가 없는 메소드라는 의미다
- 즉, 하위 클래스가 상속해서 이 추상 메소드를 구현해야한다
- private methods
-
private [static] 리턴타입 메소드명(매개변수, ...) { ... }- private method는 interface 내에서만 사용할 수 있다
- private static method는 static & non-static 인터페이스 메소드 안에서 사용할 수 있다
- private non-static method는 private static method 내에서 사용할 수 없다
- private method를 가지는
CustomInterface예제
public interface CustomInterface {
public abstract void method1();
public default void method2() {
method4(); // default method 내 private method 호출
method5(); // non-static method 내 private static method 호출
System.out.println("default method");
}
public static void method3() {
method5(); // static method 내 private static method 호출
System.out.println("static method");
}
private void method4() {
System.out.println("private method");
}
private static void method5() {
System.out.println("private static method");
}
}CustomInterface를 구현한CustomClass
public class CustomClass implements CustomInterface {
@Override
public void method1() {
System.out.println("abstract method");
}
public static void main(String[] args) {
CustomInterface instance = new CustomClass();
instance.method1();
instance.method2();
CustomInterface.method3();
}
}
// Output
/*
* abstract method
* private method
* private static method
* default method
* private static method
* static method
*/-
두 가지 기능이 있는 계산기 클래스 예제
- 입력 받은 숫자들 중 짝수의 합을 구하는 기능 (메소드)
- 입력 받은 숫자들 중 홀수의 합을 구하는 기능 (메소드)
-
CustomCalculator인터페이스import java.util.function.IntPredicate; import java.util.stream.IntStream; public interface CustomCalculator { default int addEvenNumbers(int... nums) { return add(n -> n % 2 == 0, nums); } default int addOddNumbers(int... nums) { return add(n -> n % 2 != 0, nums); } private int add(IntPredicate predicate, int... nums) { return IntStream.of(nums) .filters(predicate) .sum(); } }
-
CustomCalculator를 구현한Main클래스public class Main implements CustomCalculator { public static void main(String[] args) { CustomCalculator demo = new Main(); int sumOfEvens = demo.addEvenNumbers(1,2,3,4,5,6,7,8,9); System.out.println(sumOfEvens); int sumOfOdds = demo.addOddNumbers(1,2,3,4,5,6,7,8,9); System.out.println(sumOfOdds); } } // Output /* * 20 * 25 */
다형성 (polymorphism)
- 하나의 타입에 대입되는 객체에 따라서, 실행 결과가 다양한 형태로 나오는 성질
- 부모 타입에 어떤 자식 객체를 대입하느냐에 따라 실행 결과가 달라지듯이, 인터페이스 타입에 어떤 구현 객체를 대입하느냐에 따라 실행 결과가 달라짐
- 상속과 인터페이스 모두 다형성을 구현하는 기술
- 상속
- 같은 종류의 하위 클래스를 만드는 기술
- 인터페이스
- 사용 방법이 동일한 클래스를 만드는 기술
-
개발 시, 인터페이스를 사용해서 메소드를 호출하도록 코딩하면
-
구현 객체를 교체하는 것은 매우 빠르게 교체 가능하다
- 프로그램 소스 코드는 변함이 없는데, 구현 객체를 교체해서 프로그램 실행 결과가 다양해짐
-
Q.
A클래스를 이용해서 프로그램을 개발하는데, 개발 완료 후B클래스로 바꾸기로 했을 때, 어떻게 해야 할까?- 그런데,
B클래스의 메소드는A클래스의 메소드와 이름, 매개 변수가 다르다.
- 그런데,
-
이렇게 되면, 코드 상에서
A클래스의 메소드가 사용된 곳을 찾아 B 클래스의 메소드로 변경해야 함A클래스와B클래스의 메소드 선언부가 동일하다면 어떻게 될까?- 인터페이스를 작성하고
A,B클래스는 구현 클래스로 작성하면 되지 않을까?
- 인터페이스를 작성하고
[출처] : 이것이 자바다 - 신용권의 Java 프로그래밍 정복 1권 p.363
A클래스와B클래스 모두I인터페이스를 이용해서 클래스를 구현하고,- 다른 클래스를 사용해야 할 경우, 구현 객체를 교체한다
-
인터페이스 타입으로 매개 변수를 선언하면 메소드 호출 시,
-
useRemoteControl()메소드의 매개 변수가RemoteControl타입- 매개값으로
Television객체 orAudio객체를 선택적으로 줄 수 있음
- 매개값으로
-
자동 타입 변환 (Promotion) - 실행 도중 자동적으로 타입 변환이 일어나는 것
[출처] : 이것이 자바다 - 신용권의 Java 프로그래밍 정복 1권 p.364
-
인터페이스 구현 클래스를 상속해서 자식 클래스를 만들었다면
- 자식 객체도 인터페이스 타입으로 자동 변환시킬 수 있다
-
자동 타입 변환을 이용하여,
-
필드의 다형성
-
매개 변수의 다형성
을 구현할 수 있음
-
-
설계 시, 필드 타입으로 타이어 인터페이스를 설계하면
→ 필드 값으로 한국 타이어 또는 금호 타이어 객체를 대입할 수 있다
→ 자동 타입 변환이 일어나므로 아무런 문제가 없다
-
Tire인터페이스
// 인터페이스
public interface Tire {
public void roll(); // roll() 메소드 호출 방법 설명
}Tire인터페이스의 구현클래스HankookTire
// 구현 클래스
public class HankookTire implements Tire {
@Override // Tire 인터페이스 구현
public void roll() {
System.out.println("한국 타이어가 굴러갑니다.");
}
}Tire인터페이스의 구현 클래스KumhoTire
// 구현 클래스
public class KumhoTire implements Tire {
@Override
public void roll() {
System.out.println("금호 타이어가 굴러갑니다.");
}
}Car클래스
// 필드 다형성
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();
}
}CarExample클래스
// 필드 다형성 테스트
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();
}
}- 위의
Car클래스에서 4개의 타이어 필드를 인터페이스로 각각 선언했지만, 아래와 같이 인터페이스 배열로 관리할 수도 있다
Tire[] tires = {
new HankookTire(),
new HankookTire(),
new HankookTire(),
new HankookTire()
};-
각
Tire들이 인덱스로 표현되므로, 대입이나 제어문에서 활용하기 좋다 -
tires-
구현 객체인
KumhoTire를 대입하면, 자동으로 타입 변환이 발생한다tires[1] = new KumhoTire(); // 자동으로 타입 변환 (Promotion)
-
-
제어문에서 가장 큰 이점이 있다
-
전체 타이어의
roll()메소드를 호출하는Car클래스의run()메소드를 아래와 같이 수정할 수 있다void run() { for (Tire tire : tires) { tire.roll(); } }
-
Car클래스의 타이어 필드를 배열로 수정한 예제
// 인터페이스 배열로 구현 객체 관리
public class Car {
Tire[] tires = {
new HankookTire(),
new HankookTire(),
new HankookTire(),
new HankookTire()
};
void run() {
for (Tire tire : tires) {
tire.roll();
}
}
}Car클래스를 수정하여CarExample도 수정
// CarExample 클래스도 수정
public class CarExample {
public static void main(String[] args) {
Car myCar = new Car();
myCar.run();
myCar.tires[0] = new KumhoTire();
myCar.tires[1] = new KumhoTire();
myCar.run();
}
}- 메소드를 호출할 때도 많이 발생하는 자동 타입 변환
- 매개값을 다양화 하기 위해
- 상속
- 매개 변수를 부모 타입으로 선언 & 호출 시 자식 객체를 대입
- 인터페이스
- 매개 변수를 인터페이스 타입으로 선언 & 호출 시 구현 객체를 대입
- 상속
public class Driver {
public void drive(Vehicle vehicle) {
vehicle.run(); // 구현 객체의 run() 메소드가 실행됨
}
}
public interface Vehicle {
public void run();
}[출처] : 이것이 자바다 - 신용권의 Java 프로그래밍 정복 1권 p.371
-
drive()메소드는Vehicle타입을 매개 변수로 선언했지만,Vehicle을 구현한Bus객체가 매개 값으로 사용되면 자동 타입 변환이 발생한다 -
매개 변수의 타입이 인터페이스일 경우, 어떠한 구현 객체도 매개값으로 사용할 수 있고,
- 어떤 구현 객체가 제공되느냐에 따라 메소드의 실행 결과는 다양해질 수 있음
- 매개 변수의 다형성
Driver클래스
// 매개 변수의 인터페이스화
public class Driver {
public void drive(Vehicle vehicle) {
vehicle.run();
}
}Vehicle인터페이스
// 인터페이스
public interface Vehicle {
public void run();
}Vehicle인터페이스를 구현한Bus클래스
// 구현 클래스
public class Bus implements Vehicle {
@Override
public void run() {
System.out.println("버스가 달립니다.");
}
}Vehicle인터페이스를 구현한Taxi클래스
// 구현 클래스
public class Taxi implements Vehicle {
@Override
public void run() {
System.out.println("택시가 달립니다.");
}
}- 매개 변수의 다형성을 테스트할
DriverExample클래스
// 매개 변수의 다형성 테스트
public class DriverExample {
public static void main(String[] args) {
Driver driver = new Driver();
Bus bus = new Bus();
Taxi taxi = new Taxi();
driver.drive(bus); // 자동 타입 변환 : Vehicle vehicle = bus;
driver.drive(taxi); // 자동 타입 변환 : Vehicle vehicle = taxi;
}
}
/* Result
버스가 달립니다.
택시가 달립니다.
*/-
구현 객체가 인터페이스 타입으로 자동 변환하면
- 인터페이스에 선언된 메소드만 사용 가능하다는 제약 사항
-
ex)
-
하지만, 경우에 따라 구현 클래스에 선언된 필드와 메소드를 사용해야 하는 경우도 있다
[출처] : 이것이 자바다 - 신용권의 Java 프로그래밍 정복 1권 p.374
Vehicle인터페이스
// 인터페이스
public interface Vehicle {
public void run();
}Vehicle인터페이스를 구현한Bus클래스
// 구현 클래스
public class Bus implements Vehicle {
@Override
public void run() {
System.out.println("버스가 달립니다.");
}
public void checkFare() {
System.out.println("승차요금을 체크합니다.");
}
}- 강제 타입 변환을 테스트할
DriverExample클래스
// 강제 타입 변환
public class VehicleExample {
public static void main(String[] args) {
Vehicle vehicle = new Bus();
vehicle.run();
// vehicle.checkFare(); // Vehicle 인터페이스에는 checkFare() 가 없어서 호출 불가
Bus bus = (Bus) vehicle; // 강제 타입 변환
bus.run();
bus.checkFare(); // Bus 클래스에는 checkFare()가 있음
}
}
/* Result
버스가 달립니다.
버스가 달립니다.
승차요금을 체크합니다.
*/-
그러나 어떤 구현 객체가 변환되어 있는지 알 수 없는 상태에서 무작정 변환을 할 경우
-
ClassCastException이 발생할 수 있다Vehicle vehicle = new Taxi(); Bus bus = (Bus) vehicle; // ClassCastException 발생
-
메소드의 매개 변수가 인터페이스로 선언된 경우, 메소드를 호출할 때 다양한 구현 객체들을 매개값으로 지정할 수 있다 (매개 변수의 다형성)
-
어떤 구현 객체가 지정될 지 모르는 상황에서 다음과 같이 매개값을
Bus로 강제 타입 변환하면ClassCastException이 발생 할 수 있다public void drive(Vehicle vehicle) { Bus bus = (Bus) vehicle; bus.checkFare(); vehicle.run(); }
-
-
그러면, 어떤 구현 객체가 인터페이스 타입으로 변환되었는지 확인하는 방법은 없는걸까?
-
instanceof이용하면 됨 -Vehicle인터페이스 타입으로 변환된 객체가Bus인지 확인하면 된다if (vehicle instanceof Bus) { Bus bus = (Bus) vehicle; }
-
→ 매개값이 어떤 객체인지
instanceof연산자로 확인하고 안전하게 강제 타입 변환 해야한다// instanceof 로 객체 타입 확인 public class Driver { public void drive(Vehicle vehicle) { // vehicle 매개 변수가 참조하는 객체가 Bus인지 조사 if (vehicle instanceof Bus) { Bus bus = (Bus) vehicle; // Bus 객체인 경우 안전하게 강제 타입 변환 bus.checkFare(); // Bus } vehicle.run(); } }
-
- 신용권, 『이것이 자바다』, 한빛미디어(2015), p.344 ~ p.385
- 이상민, 『자바의 신』, 로드북(2018), p.310 ~ p.317
- https://howtodoinjava.com/java9/java9-private-interface-methods#java9
- https://www.journaldev.com/12850/java-9-private-methods-interfaces#java-9-interface-changes









