[Effective Java] 아이템21 - 인터페이스는 구현하는 쪽을 생각해 설계하라
자바8부터는 기존 구현체를 수정하지 않아도 인터페이스에 디폴트 메소드를 추가할 수 있게되었다. 주로 람다를 활용하기 위해서다.
대부분 문제가 생기지 않지만, 모든 상황을 예측하기는 어렵다.
모든 구현체에 문제가 없는 것은 아니다.
Collection 인터페이스의 removeIf
가 그 중 하나다.
이 메소드는 매개변수로 주어진 Predicate
이 true면 모든 원소를 제거한다.
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;
}
}
모든 Collection
구현체와 잘 어우러지고 있지만, org.apache.commons.collections4.collection.SynchronizedCollection
클래스에서는 문제가 있다.
이 클래스는 컬렉션 대신 클라이언트가 제공한 객체로 락을 거는 능력을 추가로 제공한다. 즉, 모든 메서드에서 주어진 락 객체로 동기화한 후 내부 컬렉션 객체에 (기존 SynchronizedCollection의)기능을 위임하는 래퍼 클래스다.
이 클래스를 자바 8과 함께 사용한다면, 그래서 removeIf
의 디폴트 구현을 물려받게 된다면, 다신이 한 약속을 더 이상 지키지 못한다. 왜냐하면 removeIf
의 구현은 동기화에 관해 아무것도 모르므로 락 객체를 사용할 수 없다.
멀티 스레드 환경에서 한 스레드가 removeIf
를 호출하면 ConcurrentModificationException
이 발생할 수 있다.
자바 플랫폼 라이브러리에서 이런 문제를 예방하기 위해 디폴트 메서드 호출 전 필요한 작업을 수행하도록 해놨다.
디폴트 메서드는 런타임 오류를 일으킬 수 있다.
흔한 일은 아니지만, 자바8은 컬렉션 인터페이스에 꽤 많은 디폴트 메서드를 추가했고, 그 결과 기존에 짜여진 많은 자바 코드가 영향을 받은 것으로 알려졌다.
따라서, 기존 인터페이스에 디폴트 메서드로 새 메서드를 추가하는 일은 꼭 필요한 경우가 아니면 피해야 한다. 대신, 신규 인터페이스인 경우에는 아주 유용한 수단이다.
참고 - Iterator 와 ConcurrentModificationException
SynchronizedCollection
을 처음 읽어봐서 구글링해서 이 글을 읽어보고 정리한다.
Collection
클래스의 값을 반복시켜 읽어내는 가장 표준적인 방법은 바로 Iterator
를 사용하는 방법이다. Iterator
를 사용해 컬렉션 클래스 내부의 값을 차례로 읽고 사용하는 동안 다른 스레드가 같은 시점에 컬렉션 클래스 내부의 값을 변경하는 작업을 처리하지는 못하게 되어 있고, 대신 즉시 멈춤(fail-fast) 의 형태로 반응하도록 되어 있다.
즉시멈춤이란 반복문을 실행하는 도중에 컬랙션 클래스 내부의 값을 변경하는 상황이 포착되면 그 즉시 ConcurrentModificationException 예외를 발생시키고 멈추는 처리 방법이다.
댓글남기기