Java8 이전
- 기존 구현체를 깨뜨리지 않고는 인터페이스에 메서드를 추가할 수 있는 방법이 없었다.
- "현재 인터페이스에 새로운 메서드가 추가될 일은 영원히 없다"
- 요구사항 추가 -> 인터페이스에 메서드 추가 -> 모든 구현체 클래스 변경
Java8 이후
기존 인터페이스에 메서드를 추가할 수 있는 디폴트 메서드
가 추가되었다.
- 디폴트 메서드는 완전한 것이 아니다. 기존 클래스에 여러 문제를 야기할 수 있다.
- 디폴트 메서드는 꼭 필요한 경우가 아니면 사용을 피해라.
- 인터페이스를 설계할 때는 여전히 세심한 주의를 기울여야 한다.
- 인터페이스를 설계할 때는 추후 변경사항이 발생하지 않도록 릴리즈 전 많은 테스트 과정을 거쳐야한다.
디폴트 메서드는 인터페이스에 있는 구현 메서드를 의미한다.
- 메서드 앞에
deault 에약어
가 붙는다. 구현부 {}
가 있어야 한다.
다음은 List 인터페이스의 디폴트 메서드와 추상 메서드이다.
// 디폴트 메서드
default void sort(Comparator<? super E> c) {
Object[] a = this.toArray();
Arrays.sort(a, (Comparator) c);
ListIterator<E> i = this.listIterator();
for (Object e : a) {
i.next();
i.set((E) e);
}
}
// 추상 메서드
void clear();
장점
- 기존 추상 메서드와 다르게 인터페이스 안에서 디폴트 구현을 지정할 수 있다.
- 디폴트 메서드를 재정의하지 않은 모든 클래스에서는 디폴트 구현을 사용하며, 재정의해 사용할 수도 있다.
단점
- 디폴트 메서드는 구현 클래스에 대해 아무것도 모른 채 합의 없이 무작정 삽입될 뿐이므로 주의해야한다.
- Java8에서는 람다를 제공하기 위해 핵심 컬렉션 인터페이스들에 다수의 디폴트 메서드들을 추가하였다.
- 코드 품질이 높고 범용적이라 대부분의 상황에서 잘 작동한다.
- 하지만 모든 상황을 커버할 수는 없다.
public interface Collection<E> extends Iterable<E> {
default boolean removeIf(Predicate<? super E> filter) {
Objects.requireNonNull(filter);
boolean removed = false;
final Iterator<E> each = iterator();
while (each.hasNext()) {
if (filter.test(each.next())) {
each.remove();
removed = true;
}
}
return removed;
}
}
org.apache.commons.collections4.collection.SynchronizedCollection
와의 호환 문제
SynchronizedCollection
은 동기화를 제공해 멀티 스레드 환경에서 안정성을 보장해준다.- 하지만
removeIf()
를 재정의하고 있지 않다. removeIf()
는 동기화에 관해 아무것도 모르므로 멀티 스레드 환경에서 실행하다 보면 예기치 못한 결과를 야기할 수 있게된다. (최신 버전에서는 이슈 해결)
자바 플랫폼에서는 다음과 같은 조치를 취했다.
- 구현한 인터페이스의 디폴트 메서드를 재정의한다.
- 다른 메서드에서는 디폴트 메서드를 호출하기 전에 필요한 작업을 수행하도록 했다.
하지만 그렇다고 해서 모든 오류를 잡아낼 수 있는 것이 아니며, 컴파일에 성공하더라도 기존 구현체에 런타임 오류를 일으키는 등 다양함 문제를 야기할 수 있다. 🥲