자파 8 이전, 기존 구현체를 깨뜨리지 않고 인터페이스에 메서드를 추가할 방법이 없었다.
기존 인터페이스에 메서드를 추가할 수 있도록 디폴트 메서드 도입.
그러나 모든 기존 구현체들과 매끄럽게 연동되리라는 보장이 없다.
자바 8에서는 핵심 컬렉션 인터페이스들에 다수의 디폴트 메서드가 추가
default boolean removeIf(Predicate<? super E> filter) { //Collection 인터페이스에 추가된 디폴트 메서드
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;
}
위 코드는 범용적으로 구현되었지만, 현존하는 모든 Collection 구현체와 잘 어울러지는 것은 아니다.
대표적인 예가 아파치의 SynchronizedCollection 클래스이다.
- Collection 인터페이스 구현
- 클라이언트가 제공한 객체로 락을 거는 능력 제공
- 모든 메서드에서 주어진 락 객체로 동기화한 후, 내부 컬렉션 객체에 기능을 위임하는 래퍼 클래스
public class SynchronizedCollection<E> implements Collection<E>, Serializable {
private static final long serialVersionUID = 2412805092710877986L;
private final Collection<E> collection;
protected final Object lock;
...
protected SynchronizedCollection(Collection<E> collection) {
this.collection = (Collection) Objects.requireNonNull(collection, "collection");
this.lock = this;
}
...
public boolean add(E object) {
synchronized (this.lock) {
return this.decorated().add(object);
}
}
...
}
해당 클래스는 책이 쓰인 시점엔, removeIf 메서드를 재정의 하지 않았다.
removeIf 구현은 동기화에 관해 아무것도 고려되지 않았으므로, 해당 메서드의 구현을 물려받으면 모든 메서드 호출을 알아서 동기화한다는 약속이 깨짐.
→ 멀티 스레드 환경에서 해당 메서드 호출하면 예외를 발생하거나, 예기치 못한 결과가 생길 수 있다.
인터페이스의 디폴트 메서드를 재정의하고, 다른 메서드에서 디폴트 메서드를 호출하기 전에 필요한 작업(동기화 등..)을 수행하도록 변경
public boolean removeIf(Predicate<? super E> filter) { //동기화 하도록 재정의
synchronized(this.lock) {
return this.decorated().removeIf(filter);
}
}
- 기존 인터페이스에 디폴트 메서드를 추가하는 것은 꼭 필요한 경우가 아니면 피해라.
- 기존 구현체들과 충돌할 가능성을 고려해야 함.
- 반면, 새로운 인터페이스를 만들 때, 표준적인 메서드 구현을 제공하는 데 디폴트 메서드는 유용한 수단.