[Java] 인터페이스 ⑤

kiteB·2022년 1월 9일
0

Java

목록 보기
21/35
post-thumbnail

[ 인터페이스 상속 ]

인터페이스도 다른 인터페이스를 상속할 수 있다. 인터페이스는 클래스와는 달리 다중 상속을 허용한다. 다음과 같이 extends 키워드 뒤에 상속할 인터페이스들을 나열할 수 있다.

public interface 하위인터페이스 extends 상위인터페이스1, 상위인터페이스2 { ... }

하위 인터페이스를 구현하는 클래스는 하위 인터페이스의 메소드뿐만 아니라 상위 인터페이스의 모든 추상 메소드에 대한 실체 메소드를 가지고 있어야 한다. 그렇기 때문에 구현 클래스로부터 객체를 생성하고 나서 다음과 같이 하위 및 상위 인터페이스 타입으로 변환이 가능하다.

하위인터페이스 변수 = new 구현클래스(...);
상위인터페이스1 변수 = new 구현클래스(...);
상위인터페이스2 변수 = new 구현클래스(...);
  • 하위 인터페이스로 타입 변환: 상/하위 인터페이스에 선언된 모든 메소드 사용 가능
  • 상위 인터페이스로 타입 변환: 상위 인터페이스에 선언된 메소드만 사용 가능. 하위 인터페이스에 선언된 메소드는 사용 불가

예를 들어 아래와 같이 인터페이스가 상속 관계에 있다고 가정해보자.

  • interfaceC 인터페이스 변수는 methodA(), methodB(), methodC() 모두 호출 가능
  • interfaceAmethodA()만 호출 가능
  • interfaceBmethodB()만 호출 가능

예제

  • InterfaceA (부모 인터페이스)
public interface InterfaceA {
    public void methodA();
}
  • InterfaceB (부모 인터페이스)
public interface InterfaceB {
    public void methodB();
}
  • InterfaceC (하위 인터페이스)
public interface InterfaceC extends InterfaceA, InterfaceB {
    public void methodC();
}
  • ImplementationC (하위 인터페이스 구현)
public class ImplementationC implements InterfaceC {

    //InterfaceA와 InterfaceB의 실체 메소드도 있어야 한다.
    public void methodA() {
        System.out.println("ImplementationC-methodA() 실행");
    }
    
    public void methodB() {
        System.out.println("ImplementationC-methodB() 실행");
    }
    
    public void methodC() {
        System.out.println("ImplementationC-methodC() 실행");
    }
    
}
  • Example
public class Example {
    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();
    }
}
  • 실행 결과
ImplementationC-methodA() 실행

ImplementationC-methodB() 실행

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

[ 디폴트 메소드와 인터페이스 확장 ]

디폴트 메소드
: 인터페이스에 선언된 인스턴스 메소드. 구현 객체가 있어야 사용할 수 있다.

디폴트 메소드는 모든 구현 객체에서 공유하는 기본 메소드처럼 보이지만, 사실은 인터페이스에서 디폴트 메소드를 허용한 다른 이유가 있다.


1. 디폴트 메소드의 필요성

인터페이스에서 디폴트 메소드를 허용하는 이유
기존 인터페이스를 확장해서 새로운 기능을 추가하기 위해서이다.

기존 인터페이스의 이름과 추상 메소드의 변경 없이 디폴트 메소드만 추가할 수 있기 때문에이전에 개발한 구현 클래스를 그대로 사용할 수 있으면서 새롭게 개발하는 클래스는 디폴트 메소드를 활용할 수 있다.

MyInterface라는 인터페이스와 이를 구현한 MyClassA라는 클래스가 있다고 가정해보자.

시간이 흘러 MyInterface에 기능을 추가해야 하는 일이 생겨서 MyInterface에 추상 메소드를 추가했다고 생각해보자. 만약 MyClassA를 수정할 여건이 안된다면, 추가된 추상 메소드에 대한 실체 메소드가 MyClassA가 없으므로 문제가 발생하게 된다.

이렇게 MyClassA를 수정할 여건이 안된다면 MyInterface에는 추상 메소드를 추가할 수 없다. 이럴 때 MyInterface를 디폴트 메소드를 추가하는 것이다.

디폴트 메소드는 추상 메소드가 아니기 때문에 구현 클래스에서 실체 메소드를 작성할 필요가 없어 MyClassA를 아무런 문제 없이 사용할 수 있게 된다.

수정된 MyInterface를 구현한 새로운 클래스인 MyClassBmethod1()의 내용은 반드시 채워야 하지만, 디폴트 메소드인 method2()MyInterface에 정의된 것을 사용해도 되고, 필요에 따라 재정의할 수도 있다.

예제

  • MyInterface (기존 인터페이스)
public interface MyInterface {
    public void method1();
}
  • MyClassA (기존 구현 클래스)
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() 실행");
    }
}
  • MyClassB (새로운 구현 클래스)
public class MyClassB implements MyInterface {
    @Override 
    public void method1() {
        System.out.println("MyClassB-method1() 실행");    
    }
    
    //디폴트 메소드 재정의
    @Override
    public default void method2() {
        System.out.println("MyClassB-method2() 실행");
    }
}
  • DefaultMethodExample (디폴트 메소드 사용)
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();
    }
}
  • 실행 결과
MyClassA-method1() 실행
MyInterface-method2() 실행
MyClassB-method1() 실행
MyClassB-method2() 실행

2. 디폴트 메소드가 있는 인터페이스 상속

부모 인터페이스에 디폴트 메소드가 정의되어 있을 경우, 자식 인터페이스에서 디폴트 메소드를 활용하는 방법은 다음 세 가지이다.

📌 자식 인터페이스에서 디폴트 메소드를 활용하는 방법

  • 디폴트 메소드를 단순히 상속만 받는다.
  • 디폴트 메소드를 재정의(Override)해서 실행 내용을 변경한다.
  • 디폴트 메소드를 추상 메소드로 재선언한다.

다음과 같이 추상 메소드와 디폴트 메소드가 선언된 ParentInterface가 있다고 가정해보자.

0) ParentInterface (부모 인터페이스)

public interface ParentInterface {
    public void method1();	//추상 메소드
    public default void method2() { /*실행문*/ }	//디폴트 메소드
}

1) ChildInterface1 (자식 인터페이스 - 단순히 상속만 받는다.)

public interface ChildInterface1 extends ParentInterface {
    public void method3();
}

ChildInterface1 인터페이스를 구현하는 클래스는

  • method1()method3()의 실체 메소드를 가지고 있어야 하며,
  • ParentInterfacemethod2()를 호출할 수 있다.
ChildInterface ci1 = new ChildInterface1() {
    @Override
    public void method1() { /*실행문*/ }
    
    @Override
    public void method3() { /*실행문*/ }
};

ci1.method1();
ci1.method2();	//ParentInterface의 method2() 호출
ci1.method3();

2) ChildInterface2 (자식 인터페이스 - 재정의한다.)

public interface ChildInterface1 extends ParentInterface {
    @Override
    public default void method2() { /*실행문*/ }	//재정의

    public void method3();
}

ChildInterface2 인터페이스를 구현하는 클래스는

  • method1()method3()의 실체 메소드를 가지고 있어야 하며,
  • ChildInterface2에서 재정의한 method2()를 호출할 수 있다.
ChildInterface ci2 = new ChildInterface2() {
    @Override
    public void method1() { /*실행문*/ }
    
    @Override
    public void method3() { /*실행문*/ }
};

ci2.method1();
ci2.method2();	//ChildInterface2의 method2() 호출
ci2.method3();

3) ChildInterface2 (자식 인터페이스 - 디폴트 메소드를 추상 메소드로 재선언한다.)

public interface ChildInterface1 extends ParentInterface {
    public void method2();	//추상 메소드로 재선언
    public void method3();
}

ChildInterface3 인터페이스를 구현하는 클래스는

  • method1()method2(), method3()의 실체 메소드를 모두 가지고 있어야 한다.
ChildInterface ci3 = new ChildInterface3() {
    @Override
    public void method1() { /*실행문*/ }
    @Override
    public void method2() { /*실행문*/ }
    @Override
    public void method3() { /*실행문*/ }
};

ci3.method1();
ci3.method2();	//ChildInterface3 구현 객체의 method2() 호출
ci3.method3();
profile
🚧 https://coji.tistory.com/ 🏠

0개의 댓글