[교재 EffectiveJava] 아이템 21. 인터페이스는 구현하는 쪽을 생각해 설계하라

반응형
728x90
반응형

디폴트 메서드의 문제

자바 8 전에는 기존 구현체를 깨뜨리지 않고는 인터페이스에 메서드를 추가할 방법이 없었다. 인터페이스에 메서드를 추가하면 컴파일 오류가 발생한다. 인터페이스에 메서드를 추가할 수 있도록 디폴트 메서드가 자바 8이후로 출현했지만 위험이 완전히 사라진건 아니다. 디폴트 메서드를 선언하면, 그 인터페이스를 구현한 후 디폴트 메서드를 재정의하지 않은 모든 클래스에서 디폴트 구현이 쓰이게 된다. 모든 기존 구현체들과 매끄럽게 연동되진 않는다. 디폴트 메서드는 구현 클래스에 대해 아무것도 모른채 합의 없이 무작정 삽입될 뿐이다.

 

 

문제상황

자바 8에서 컬렉션 인터페이스들에 다수의 디폴트 메서드가 추가됬지만, 모든 상황에서 불변식을 해치지 않는 디폴트 메서드를 작성하긴 어려웠다. Collection 인터페이스의 removeIf 메서드에서는 boolean 함수가 true 를 반환하는 모든 원소를 제거한다. 디폴트 구현은 반복자를 이용하여 순회하면서 각 원소를 인수로 넣어 boolean 함수를 호출하고, boolean 함수가 true 를 반환하면 반복자의 remove 메서드를 호출하여 그 원소를 제거한다.

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

 

이 코드는 SynchronizedCollection 클래스 (apache) 와 잘 어우러지지 않는다. SynchronizedCollection 클래스는 클라이언트가 제공한 객체로 락을 거는 능력이 제공되어, 모든 메서드에서 주어진 락 객체로 동기화한 후 내부 컬렉션 객체에 기능을 위임하는 래퍼 클래스다. 또한 removeIf 메서드를 재정의 하지 않고있다. 이 클래스를 자바 8과 함께 사용하여 removeIf의 디폴트 구현을 물려받게 된다면, 모든 메서드 호출을 알아서 동기화해주지 못한다. removeIf 의 구현은 동기화에 관해 아무것도 모르므로 락 객체를 사용할 수 없다. SynchronizedCollection 인스턴스를 여러 스레드가 공유하는 환경에서 한 스레드가 removeIf 를 호출하면 ConcurrentModificationException 이 발생하거나, 다른 예기치 못한 결과로 이어질 수 있다.

 

 

결론

디폴트 메서드는 기존 구현체에 런타임 오류를 일으킬 수 있다. 자바 8 컬렉션 인터페이스에 많은 디폴트 메서드가 추가되었고, 이미 짜여진 많은 자바 코드가 영향을 받았다. 기존 인터페이스에 디폴트 메서드로 새 메서드를 추가하는 일은 왠만하면 피해야한다. 인터페이스를 설계할 때는 여전히 세심한 주의를 기울여야한다.

 

반응형

Designed by JB FACTORY