자바 8 전에는 기존 구현체를 깨뜨리지 않고 인터페이스에 메서드를 추가할 방법은 존재하지 않았다. 자바 8부터 디폴트 메서드를 통해서 기존 인터페이스에 메서드를 추가할 수 있게 되었다.
디폴트 메서드를 선언하면, 그 인터페이스를 구현한 후 디폴트 메서드를 재정의하지 않은 모든 클래스에서 디폴트 구현이 쓰이게 된다.
단, 이렇게 디폴트 메서드를 추가한다고해도 기존 구현체들과 매끄럽게 연동된다는 보장은 없다.
생각할 수 있는 모든 상황에서 불변식을 해치지 않는 디폴트 메서드를 작성하기란 어렵기 때문이다.
[Çollection의 removeIf 디폴트 메서드]
default boolean removeIf(Predicate<? super E> filter) {
Objects.requireNonNull(filter);
boolean result = false;
for (Iterator<E> it = iterator(); it.hasNext();){
if(filter.test(it.next())){
it.remove();
result = true;
}
}
return result;
}
이 코드는 범용적으로 작성되었지만 현존하는 모든 Collection
구현체와 잘 어우러지는것은 아니다. 대표적인 예가 SyncronizedCollection
이다.
아파치 커먼즈 라이브러리의 이 클래스는 클라이언트가 제공한 객체로 락을 거는 능력을 추가로 제공한다. 즉 모든 메서드에서 주어진 락 객체로 동기화한 후 내부 컬렉션 객체에 기능을 위임하는 래퍼 클래스다.
현재는 SynchronizedCollection
클래스가 removeIf
메서드를 재정의하고 있지 않고있다. removeIf
메서드는 동기화에 관해 아무것도 알지 못하므로 락 객체를 사용할 수가 없다.
SynchronizedCollection
인스턴스를 여러 스레드가 공유하는 환경에서 한 스레드가 removeIf
를 호출하면 ConcurrentModificationException
이 발생하거나 다른 오류가 발생할 가능성이 있다.
[4.4 버전 이후로 동기적으로 구현]
/**
* @since 4.4
*/
@Override
public boolean removeIf(final Predicate<? super E> filter) {
synchronized (lock) {
return decorated().removeIf(filter);
}
}
4.4 이전 버전에서는 동기적으로 구현되지 않아 removeIf
호출시 default
메서드를 호출하게되어 동기적으로 동작하지 못했다.
4.4 이후 버전에서는 Collection.synchronizedCollection
이 반환하는 package-private
클래스들은 removeIf
를 재정의하고, 이를 호출하는 다른 메서드들은 디폴트 구현을 호출하기 전에 동기화 하도록 했다.
디폴트 메서드는 컴파일에 성공하더라도 기존 구현체에 런타임 오류를 일으킬수 있다. 때문에 기존 인터페이스에 디폴트 메서드로 새 메서드를 추가하는일은 피해야한다.
반면, 새로운 인터페이스를 만드는 경우라면 표준적인 메서드 구현을 제공하는데 아주 유용한 수단이며, 그 인터페이스를 더 쉽게 구현해 활용할 수 있게끔 해준다.
새로운 인터페이스라면 릴리스 전에 반드시 테스트를 거쳐야한다. 서로 다른 방식으로 최소 세가지는 구현해보는것을 추천한다. 또한 다양한 클라이언트도 만들어 보는것이 좋다.
이런 테스트 과정을 통해 결함을 찾아내야 한다. 인터페이스를 릴리스 한 후라도 결함을 수정하는게 가능할 경우도 있겠지만, 그 가능성에 기대서는 안된다.