[Ch13] 디폴트 메서드
인터페이스에 새로운 메서드를 추가하려면 해당 인터페이스를 구현한 모든 클래스의 구현도 고쳐야한다. 이러한 단점을 보완하기 위해 자바8에서는 정적 메서드와 디폴트 메서드를 사용한다.
결과적으로 기존 인터페이스를 구현하는 클래스는 자동으로 인터페이스에 추가된 새로운 메서드의 디폴트 메서드를 상속받게 된다.
이번 챕터에서는 디폴트 메서드란 무엇이며, 어떻게 사용되는지 살펴본다.
변화하는 API
인터페이스를 고치면, 해당 인터페이스를 구현한 모든 구현체가 변경되어야 한다. 만약, 새로 추가된 메서드를 구현하지 않은 인스턴스에서 new method 를 호출했을 경우 아래와 같은 에러가 발생할 것이다.
Exception in thread “main” java.lang.AbstractMethodError: 구현체.새로운메서드
디폴트 메서드란 무엇인가?
호환성을 유지하면서 API 를 바꿀 수 있도록 새로운 기능인 디폴트 메서드 를 제공한다.
public interface Sized {
int size();
default boolean isEmpty() { // 디폴트 메서드
return size() == 0;
}
}
Sized
인터페이스를 구현하는 모든 클래스는 isEmpty
의 구현도 상속받는다. isEmpty
메서드가 필요없는 구현체는 이 메서드를 구현할 필요없고, 호출을 안하면 되는것이다.
자바8 API 에서 디폴트 메서드가 상당히 많이 활용되었음을 알 수 있다. Collection
인터페이스에 stream
메서드도 default 메서드로 추가된 것이다.
메서드가 구현체에 상속된다는 점에서 추상 클래스와 비슷하다는 느낌을 받는데, 클래스는 하나의 추상 클래스만 상속받을 수 있지만, 여러개의 인터페이스를 구현할 수 있다는 점에서 다르다.
디폴트 메서드 활용 패턴
라이브러리 변경을 위한 목적으로 쓰일 수도 있지만, 우리가 만드는 인터페이스에도 활용 할 수 있다.
선택형 메서드 (optional method)
인터페이스를 구현하는 클래스에서 메서드의 내용이 비어있는 상황을 본적이 있을 것이다. 인터페이스에 메서드가 있지만, 굳이 구현체에서는 구현할 필요가 없는 경우이다.
디폴트 메서드를 이용하면 그러한 메서드에 기분 구현을 제공할 수 있으므로 인터페이스를 구현하는 클래스에서 빈 구현을 제공할 필요가 없다.
interface Iterator<T> {
boolean hasNext();
T next();
default void remove() {
throw new UnsupportedOperationException();
}
}
remove()
메서드를 사용하려면 구현체에서 반드시 override 해서 구현하도록 설계된 인터페이스다.
동작 다중 상속 (multiple inheritance of behavior)
자바에서 클래스는 한 개의 다른 클래스만 상속할 수 있지만, 인터페이스는 여러 개 구현할 수 있다.
public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, Seriazliable { // 4 개의 인터페이스를 구현한다.
}
각 인터페이스가 제공하는 기능이 중복되지 않도록 설계한다면, ArrayList 처럼 여러개의 (중복되지 않는) 인터페이스를 상속받아서 쉽게 재사용하고 조합할 수 있다.
디폴트 메서드를 사용하면, 위와 같이 여러개의 인터페이스를 상속해도, 모든 메서드를 구현할 필요가 없기 때문에 보다 유연하고 깔끔한 구현체를 구현할 수 있다.
해석 규칙
그렇다면 구현체에서 N개의 인터페이스를 상속받았을 경우, default 메서드의 시그니처가 겹치면 어떤 인터페이스의 메서드를 사용하게 될까?
- 클래스가 항상 이긴다. 클래스나 슈퍼클래스에서 정의한 메서드가 디폴트 메서드보다 우선권을 갖는다.
- 1번 규칙 이외의 상황에서는 서브인터페이스가 이긴다. B가 A를 상속받는다면 B가 A를 이긴다.
- 여전히 ambiguous 하다면 구현체가 명시적으로 디폴트 메서드를 오버라이드하고 호출해야 한다.
댓글남기기