인터페이스 ④
인터페이스 상속
인터페이스도 다른 인터페이스를 상속할 수 있다.
인터페이스는 클래스와는 달리 다중 상속을 허용한다.
public interface 하위인터페이스 extends 상위인터페이스1, 상위인터페이스2{ ... }
하위 인터페이스를 구현하는 클래스는 하위 인터페이스의 메소드뿐만 아니라 상위 인터페이스의 모든 추상 메소드에 대한 실체 메소드를 가지고 있어야 한다.
그렇기 때문에 구현 클래스로부터 객체를 생성하고 나서 하위 및 상위 인터페이스 타입으로 변환이 가능하다.
하위인터페이스 변수 = new 구현클래스();
상위인터페이스1 변수 = new 구현클래스();
상위인터페이스2 변수 = new 구현클래스();
하위 인터페이스로 타입 변환이 되면 상·하위 인터페이스에 선언된 모든 메소드를 사용할 수 있지만, 상위 인터페이스로 타입 변환되면 상위 인터페이스에 선언된 메소드만 사용 가능하고 하위 인터페이스에 선언된 메소드는 사용할 수 없다.

interfaceC 인터페이스 변수는 methodA(), methodB(), methodC()를 모두 호출할 수 있지만
interfaceA와 interfaceB 변수는 각각 methodA()와 methodB()만 호출할 수 있다.
public class Example {
public static void main(String[] args) {
InterfaceCImpl implC = new InterfaceCImpl();
InterfaceA a = implC;
a.methodA(); //InterfaceA 변수는 methodA()만 호출 가능
InterfaceB b = implC;
b.methodB(); //InterfaceB 변수는 methodB()만 호출 가능
InterfaceC c = implC;
c.methodA();
c.methodB(); //InterfaceC 변수는 모두 호출 가능
c.methodC();
}
}
디폴트 메소드의 필요성
인터페이스에서 디폴트 메소드를 허용한 이유는 기존 인터페이스를 확장해서 새로운 기능을 추가하기 위해서이다.
기존 인터페이스의 이름과 추상 메소드의 변경 없이 디폴트 메소드만 추가할 수 있기 때문에 이전에 개발한 구현 클래스를 그대로 사용할 수 있으면서 새롭게 개발하는 클래스는 디폴트 메소드를 활용할 수 있다.
- 기존 인터페이스에 추상메소드를 추가할 수 없다.
- 기존 인터페이스에 추상 메소드를 추가하면 기존 구현 클래스들이 모두 에러 난다. - 디폴트 메소드는 추상 메소드가 아니다.
- 디폴트 메소드를 추가하더라도 기존 구현 클래스들은 문제없이 사용할 수 있다.
- 디폴트 메소드를 재정의하는 새로운 구현 클래스를 만들 수 있다.

위에 그림을 보면 기존에 MyInterface라는 인터페이스와 이를 구현한 MyClassA라는 클래스가 있다.
시간이 흘러서 MyInterface에 기능을 추가해야 할 상황이 생겼을 때 MyInterface에 추상 메소드를 추가를 한다면 MyClassA에서 문제가 발생하게 된다. 그 이유는 추가된 추상 메소드에 대한 실체 메소드가 없이 때문이다.
MyClassA를 수정할 여건이 안 된다면 결국 MyInterface에 추상 메소드를 추가할 수 없다.
이때 MyInterface에 디폴트 메소드를 선언을 하면 디폴트 메소드는 추상 메소드가 아니기 때문에 구현 클래스에서 실체 메소드를 작성할 필요가 없기 때문에 MyClassA는 아무런 문제 없이 계속 사용이 가능하다.
수정된 MyInterface를 구현한 새로운 클래스인 MyClassB는 method1()의 내용은 반드시 채워야 하지만, 디폴트 메소드인 method2()는 MyInterface에 정의된 것을 사용해도 되고, 필요에 따라 재정의해서 사용할 수도 있다.
public interface MyInterface {
public void method1();
public default void method2(){
System.out.println("MyInterface에서 method2() 실행");
}
}
public class MyClassA implements MyInterface{
@Override
public void method1() {
System.out.println("MyClassA에서 method1() 실행");
}
}
public class MyClassB implements MyInterface{
@Override
public void method1() {
System.out.println("MyClassB에서 method1() 실행");
}
@Override
public void method2() {
System.out.println("MyClassB에서 method2() 실행");
}
}
디폴트 메소드가 있는 인터페이스 상속
부모 인터페이스에 디폴트 메소드가 정의되어 있을 경우, 자식 인터페이스에서 디폴트 메소드를 활용하는 방법은 세 가지가 있다.
- 디폴트 메소드를 단순히 상속만 받는다.
- 디폴트 메소드를 재정의해서 실행 내용을 변경한다.
- 디폴트 메소드를 추상 메소드로
ParentInterface (부모)
public interface ParentInterface {
public void method1();
public default void method2(){
System.out.println("ParentInterface에서 -> method2 실행");
}
}
.
1. 디폴트 메소드를 단순히 상속
public interface ChildInterface extends ParentInterface{
public void method3();
}
public class InterfaceExample {
public static void main(String[] args) {
ChildInterface chi1 = new ChildInterface() {
@Override
public void method1() {
System.out.println("ChildInterface에서 익명구현 객체 -> method1 실행");
}
@Override
public void method3() {
System.out.println("ChildInterface에서 익명구현 객체 -> method3 실행");
}
};
chi1.method1(); //출력 : ChildInterface에서 익명구현 객체 -> method1 실행
chi1.method2(); //출력 : ParentInterface에서 -> method2 실행
chi1.method3(); //출력 : ChildInterface에서 익명구현 객체 -> method3 실행
}
}
2. 디폴트 메소드를 재정의해서 실행 내용을 변경
public interface ChildInterface2 extends ParentInterface{
@Override
public default void method2() {
System.out.println("ChildInterface2에서 -> method2 재정의");
}
public void method3();
}
public class InterfaceExample2 {
public static void main(String[] args) {
ChildInterface2 ci2 = new ChildInterface2() {
@Override
public void method1() {
System.out.println("ChildInterface2에서 익명구현 객체 -> method1 실행");
}
@Override
public void method3() {
System.out.println("ChildInterface2에서 익명구현 객체 -> method3 실행");
}
};
ci2.method1(); //출력 : ChildInterface2에서 익명구현 객체 -> method1 실행
ci2.method2(); //출력 : ChildInterface2에서 -> method2 재정의
ci2.method3(); //출력 : ChildInterface2에서 익명구현 객체 -> method3 실행
}
}
3. 디폴트 메소드를 추상 메소드로 재선언
public interface ChildInterface3 extends ParentInterface{
@Override
public void method2(); //추상 메소드로 재선언 (default키워드 삭제)
public void method3();
}
public class InterfaceExample3 {
public static void main(String[] args) {
ChildInterface3 ci3 = new ChildInterface3() {
@Override
public void method1() {
System.out.println("ChildInterface3에서 익명구현 객체 -> method1 실행");
}
@Override
public void method2() {
System.out.println("ChildInterface3에서 익명구현 객체 -> method2 실행");
}
@Override
public void method3() {
System.out.println("ChildInterface3에서 익명구현 객체 -> method3 실행");
}
};
ci3.method1(); //출력 : ChildInterface3에서 익명구현 객체 -> method1 실행
ci3.method2(); //출력 : ChildInterface3에서 익명구현 객체 -> method2 실행
ci3.method3(); //출력 : ChildInterface3에서 익명구현 객체 -> method3 실행
}
}