디폴트 메서드라는 도구가 생겼더라도 인터페이스를 설계할 때는 여전히 세심한 주의를 기울여야 한다. 디폴트 메서드로 기존 인터페이스에 새로운 메서드를 추가하면 커다란 위험도 딸려 온다.
새로운 인터페이스라면 릴리스 전에 반드시 테스트를 거쳐야 한다. 수많은 개발자가 그 인터페이스를 나름의 방식으로 구현할 것이니, 여러분도 서로 다른 방식으로 최소한 세 가지는 구현해봐야 한다.
디폴트 메서드와 관련한 문제
- 자바 8 이전에는 기존 구현체를 깨뜨리지 않고는 인터페이스에 메서드를 추가할 방법이 없었지만, 자바 8 부터는 디폴트 메서드라는 것이 생김
- 그러나 모든 기존 구현체들과 매끄럽게 연동되리라는 보장은 없다. 디폴트 메서드는 구현 클래스에 대해 아무것도 모른 채 합의 없이 무작정 ‘삽입'될 뿐임
- 생각할 수 있는 모든 상황에서 불변식을 해치지 않는 디폴트 메서드를 작성하기란 어려운 법
- 디폴트 메서드는 (컴파일에 성공하더라도) 기존 구현체에 런타임 오류를 일으킬 수 있음
- 한편, 디폴트 메서드는 인터페이스로부터 메서드를 제거하거나 기존 메서드의 시그니처를 수정하는 용도가 아님을 명심해야 함! → 이렇게 하면 기존 클라이언트를 망가뜨리게 됨
예시
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 구현체와 문제가 있음. 해당 클래스는 java.util의 Collections.synchronizedCollection 정적 팩터리 메서드가 반환하는 클래스와 비슷함
- 아파치 버전은 클라이언트가 제공한 객체로 락을 거는 능력을 추가로 제공함
- 즉 모든 메서드에서 주어진 락 객체로 동기화한 후 내부 컬렉션 객체에 기능을 위임하는 래퍼 클래스다
- 아파치의 SynchronizedCollection 클래스는 지금도 활발히 관리되고 있지만, removeIf 메서드를 이펙티브 자바를 쓸 당시에는 재정의하지 않았다고 함 → 이 클래스를 자바 8과 함께 사용하면 removeIf 디폴트 메서드를 사용하게 되어 동기화를 적용할 수 없게 됨.