[Effective Java] 아이템 21 : 인터페이스는 구현하는 쪽을 생각해 설계하라

Loopy·2022년 7월 16일
0

이펙티브 자바

목록 보기
20/76
post-thumbnail

☁️ Default Method

자바 8 이전에는 기존 구현체를 깨뜨리지 않고는 인터페이스에 메서드를 추가할 방법이 없었지만, 자바 8부터는 디폴트 메소드 를 통해 인터페이스에 메서드를 추가하는 것이 가능해졌다.

Default 메서드 문제점

기존에 존재하던 구현체들과 연동되지 않을 수 있다.
인터페이스를 구현한 후 디폴트 메서드를 재정의하지 않은 '모든' 클래스에서도 디폴트 구현이 쓰이게 되기 때문이다.

자바 8 부터는 주로 람다를 활용하기 위해서 다수의 디폴트 메서드가 추가되었는데, 생각할 수 있는 모든 상황에서 불변식을 해치지 않는 디폴트 메서드를 작성하는 것은 어렵다. 예를 들어보자. 아래는 자바 8 에서 Collection 인터페이스에 추가된 디폴트 메서드이다.

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;
}

해당 메서드는 주어진 불리언 함수(predicate) 가 참을 반환하는 모든 원소를 제거한다. 하지만 현존하는 모든 Collection 구현체와 잘 어우러지지 않는다.

대표적인 예로, 아파치 커먼즈 라이브러리의 SynchronizedCollection 클래스는 클라이언트가 제공한 객체로 락을 거는 능력을 추가로 제공하고 있다. 모든 메서드에서 주어진 락 객체로 동기화 한 후 내부 컬렉션 객체의 기능을 위임하는 래퍼 클래스라고도 할 수 있겠다.

static class SynchronizedCollection<E> implements Collection<E>, Serializable {
        private static final long serialVersionUID = 3053995032091335093L;

        final Collection<E> c;  // Backing Collection
        final Object mutex;     // Object on which to synchronize
        
        SynchronizedCollection(Collection<E> c, Object mutex) {
            this.c = Objects.requireNonNull(c);
            this.mutex = Objects.requireNonNull(mutex);
        }

        public int size() {
            synchronized (mutex) {return c.size();}
        }
        ...
        public boolean contains(Object o) {
            synchronized (mutex) {return c.contains(o);}
        }
  }

하지만 SynchronizedCollection 클래스는 removeIf() 메서드를 재정의하고 있지 않기 때문에, 만약 자바 8과 함께 사용해 default 기능을 물려받게 된다면 모든 메서드 호출을 알아서 동기화하지 못해 ConcurrentModificationException 이 발생하게 될 것이다.

즉, 디폴트 메서드는 컴파일에 성공하더라도 기존 구현체에 런타임 오류를 일으킬 수 있다.

해결 방안

다행히도 Collection.synchronizedCollection 이 반환하는 package-private 클래스들은 removeIf 를 재정의하여, 디폴트 구현을 호출하기 전에 동기화 하도록 설계하였다.

@Override
 public boolean removeIf(Predicate<? super E> filter) {
     synchronized (mutex) {
     	return c.removeIf(filter);
     }
}

☁️ 정리

기존 인터페이스디폴트 메서드로 새 메서드를 추가하는 일은 꼭 필요한 경우가 아니면 피해야 한다. 반면, 새로운 인터페이스를 만드는 경우라면 표준적인 메서드 구현을 제공하는데 유용한 수단이며 인터페이스를 더 쉽게 구현해 활용할 수 있게끔 해준다.

https://www.geeksforgeeks.org/can-we-override-default-method-in-java/

profile
개인용으로 공부하는 공간입니다. 잘못된 부분은 피드백 부탁드립니다!

0개의 댓글