본문 바로가기

Dot Programming/Java

[Java] 자바8에 도입된 인터페이스의 디폴트 메서드 (Default Method)

    디폴트 메서드가 도입되기 전 확장성에 대한 문제점

    전통적인 자바에서 인터페이스와 관련 메서드는 한 몸처럼 구성된다. 인터페이스를 구현하는 클래스는 인터페이스에서 정의하는 모든 메서드 구현을 제공하거나 아니면 슈퍼클래스의 구현을 상속받아야 한다.

    • 구현 클래스에는 인터페이스 추상 메서드 구현이 필수이다.

     

    평소에는 이 규칙을 지키는 데 아무 문제가 없지만 라이브러리 설계자 입장에서는 인터페이스에 새로운 메서드를 추가하는 등 인터페이스를 바꾸고 싶을 때는 문제가 발생한다. 인터페이스를 바꾸면 이전에 해당 인터페이스를 구현했던 모든 클래스의 구현도 고쳐야 하기 때문이다.

    • 자바 8 API에도 List 인터페이스에 sort 같은 메서드를 추가했으므로 문제가 발생할 수 있다. 컬렉션 프레임워크인 Guava, Apache Common 등을 포함해서 기존의 List 인터페이스를 구현했던 모든 프로그래머가 sort 메서드를 구현하도록 List 인터페이스를 상속한 모든 클래스를 고쳐야 한다고 발표한다면 커뮤니티는 꽤 크게 당황할 것이다. 

     

    인터페이스의 디폴트 메서드 (Default Method)

    그래서 자바 8에서 인터페이스 내부에 static 메서드와 default 메서드를 사용할 수 있도록 새롭게 제공했다. 디폴트 메서드를 이용하면 인터페이스의 기본 구현을 그대로 상속하므로 인터페이스에 자유롭게 새로운 메서드를 추가할 수 있게 된다. 그래서 디폴트 메서드는 주로 라이브러리 설계자들이 사용한다. 왜냐하면 디폴트 메서드를 이용하면 자바 API의 호환성을 유지하면서 라이브러리를 바꿀 수 있기 때문이다.

     

    디폴트 메서드 장점

    • 기존 구현을 고치지 않고도 인터페이스를 바꿀 수 있으므로 자바 API의 호환성을 유지와 함께 확장에 용이하다.
    • 디폴트 메서드는 다중 상속 동작이라는 유연성을 제공하면서 프로그램 구성에도 도움을 준다.

     

    예로, List 인터페이스의 sort 메서드가 자바 8에 새롭게 추가된 디폴트 메서드이다.

    default void sort(Comparator<? super E> c){
    	Collections.sort(this.c);
    }

     

    디폴트 메서드 활용 패턴

    1. 선택형 메서드

    디폴트 메서드는 구현 클래스에서 직접 구현하지 않아도 오버라이딩이 되므로 굳이 구현해주지 않아도 된다. 즉 구현 클래스에서는 선택적으로 디폴트 메서드를 사용할 수 있다.

    interface Iterator<T> {
        boolean hasNext();
        T next();
        default void remove() {
            throw new UnsupportedOperationException();
        }
    }

     

    2. 동작 다중 상속

    인터페이스는 다중 상속이 가능하므로 클래스는 여러 인터페이스에서 동작을 상속 받을 수 있다.  만약 기능이 중복될 경우 결합력이 가장 높은 기능을 선택한다. 이는 아래의 해석 규칙을 참고하자.

    class Horse{
       public String identityMySelf(){
          return "I am a horse";
       }
    }
    
    interface Flyer {
       default public String identityMySelf(){
          return "I am able to fly";
       }
    }
    
    interface Mythical {
       default public String identityMySelf(){
          return "I am a mythical creature";
       }
    }
    
    public class Pegasus extends Horse implements Flyer, Mythical {
       public static void main(String[] args) {
          Pegasus myApp = new Pegasus();
          System.out.println(myApp.identityMySelf()); // "I am a horse"
       }
    }

     

    Pegasus클래스는 결합력이 가장 높은 Horse 클래스의 메서드를 오버라이딩하여 사용한다.

    Pegasus클래스 관계도

     

    해석 규칙 

    다른 클래스나 인터페이스로부터 같은 시그니처를 갖는 메서드를 상속받을 떄는 세 가지 규칙을 따라야 한다. 이는 결합력의 차이이다.

    1. 클래스가 항상 이긴다. 클래스나 슈퍼클래스에서 정의한 메서드가 디폴트 메서드보다 우선권을 갖는다.
    2. 1번 규칙 이외의 상황에서는 서브 인터페이스가 이긴다. 상속관계를 갖는 인터페이스에서 같은 시그니처를 갖는 메서드를 정의할 때는 서브 인터페이스가 이긴다. 즉, B가 A를 상속받는다면 B가 A를 이긴다.
    3. 여전히 디폴트 메서드의 우선순위가 결정되지 않았다면 여러 인터페이스를 상속받는 클래스가 명시적으로 디폴트 메서드를 오버라이드라고 호출해야 한다.

     

    사실 이 규칙은 오버라이딩, 하이딩, 클래스와 인터페이스 결합력 차이에 대해 이해한다면 따로 외우지 않아도 된다. 

     


    참고

    모던자바인액션