[Item21] 인터페이스는 구현하는 쪽을 생각해 설계해라

Sera Lee·2022년 4월 12일
0

EffactiveJava

목록 보기
9/9
post-thumbnail
post-custom-banner

Interface Default Method 사용시 주의점

  • java 8 이후로 인터페이스에 디폴트 메소드를 추가가 가능해짐
  • 특히 Collectioin Interface에 다수의 디폴트 메소드가 추가됨
  • 디폴트 메소드를 선언하면, 인터페이스 구현체에 디폴트 메서드를 정의하지 않으면 디폴트 구현이 사용된다.
  • Collection 인터페이스의 removeIf 메서드를 예시로 살펴보자

Collection Interface의 removeIf 디폴트 메소드와 apache의 SyncronizedCollection

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;
}
  • removeIf는 thread safe 하게 내부에 iterator로 Predicate를 만족하는 아이템을 삭제하도록 되어있다.
  • 아파치의 SyncronizedCollection이 Collection을 구현하고 있는데 현재 2022-04-22 기준으로는 removeIf는 메서드를 재정의하지 않고 있다.
  • SyncronizedCollection은 모든 메서드에 syncronized로 락을 걸도록 재정의를 하고 있다.
  • 즉, SyncronizedCollection은 removeIf의 디폴트 구현을 물려받고, 즉 동기화에 대해 아무것도 모르므로 여러 쓰레드환경에서는 thread safe 하지 않다.
  • 다른 라이브러리에서는 이런 문제를 예방하기 위해 조치를 취했는데,
    • 인터페이스의 디폴트 메서드를 재정의하거나,
    • 디폴트메서드를 호출하기 전에 필요한 작업을 수행하였다

예제

@Test
public void test() {
  List<String> list = new ArrayList<String>();
  list.add("1");
  list.add("2");
  list.add("3");
  list.add("4");

  SynchronizedCollection<String> sc = SynchronizedCollection.synchronizedCollection(list);

  ExecutorService executorService = Executors.newFixedThreadPool(4);
  // "1" 를 remove 하는 쓰레드가 여러개 달라붙었을 때
  for(int i = 0; i < 10; i++) {
	  executorService.submit(()->{
	    System.out.println(
	      "threadId = " + Thread.currentThread().getId() + " removeIf=" + sc.removeIf(
        p -> p.equals("1")));
    });
  }
  executorService.shutdown();
}

결과

threadId = 21 removeIf=false
threadId = 22 removeIf=true
threadId = 21 removeIf=false
threadId = 19 removeIf=false
threadId = 20 removeIf=true
threadId = 21 removeIf=false
threadId = 19 removeIf=false
threadId = 22 removeIf=false
threadId = 20 removeIf=false
threadId = 21 removeIf=false

잘 동작했다면 removeIf=true인 로그줄이 한개만 나와야 한다.

결론

  • 기존 인터페이스에 디폴트 메서드로 새 메서드를 추가하는 일은 꼭 필요한 경우에만 하자.
  • 인터페이스를 설계할 때는 구현체를 구현하여 테스트코드를 짜보자
  • 다른 개발자가 내가 의도한 대로 확장해서 사용할지 꼭 염두하자.
post-custom-banner

0개의 댓글