인터페이스의 세상은 2개로 나누어진다.
"현재의 인터페이스에 새로운 메서드가 추가될 일은 영원히 없다"고 가정하고 모든 클래스를 작성하던 자바7까지의 세상과 인터페이스에 메서드를 추가할 수 있도록 디폴트 메서드가 제공된 세상이다.
📌 자바 8 이후의 인터페이스
default method가 추가되면서 새로운 인터페이스를 구현하지 않아서 발생하는 컴파일 에러를 잡을 수 있게 되었다.
하지만 default method에도 위험은 따른다.
자바 8의 Collection 인터페이스에 추가된 removeIf 메서드를 예로 생각해보자
이 메서드는 주어진 불리언 함수(predicate)가 true를 반환하는 모든 원소를 제거한다.
- 디폴트 구현은 반복자를 이용해 순회하면서 각 원소를 인수로 넣고 predicate를 호출한다.
- predicate가 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;
}
그렇다면 이 removeIf 메서드가 정말로 하위 호환성을 지켜줄 것인가?
apache commons-collections의 4.3 버전 이하의 구현체를 보면 removeIf 메서드가 존재하지 않는다.
즉 자바 1.8 이전에 만들어진 라이브러리인데, SynchronizedCollection 클래스가 정상 작동을 할 수 있을까?
GitHub - Meet-Coder-Study/book-effective-java: 📔 이펙티브 자바 스터디 저장소
📔 이펙티브 자바 스터디 저장소. Contribute to Meet-Coder-Study/book-effective-java development by creating an account on GitHub.
github.com
그걸 진짜 테스트 하신 분들이 있네...방법 찾아보다가 감탄했습니다.
참고로 4.4 버전부터는 SynchronizedCollection에서 removeIf()를 다음과 같이 재정의하고 있어서 사용 가능하다.
public boolean removeIf(Predicate<? super E> filter) {
synchronized(this.lock) {
return this.decorated().removeIf(filter);
}
}
4.3 이하에서는 removeIf를 재정의 하지 않아서 디폴트 구현을 물려받아 모든 메서드 호출을 알아서 동기화해주지 못한다.
removeIf의 구현은 동기화에 대해 아무것도 모르므로 락 객체를 사용할 수 없으며,
SynchronizedCollection 인스턴스를 여러 스레드가 공유하는 환경에서 removeIf를 호출하면 ConcurrentModificationException이 발생하거나 예상치 못한 결과로 이어질 수 있다.
💡 디폴트 메서드는 컴파일에 성공하더라도 기존 구현체에 런타임 오류를 일으킬 수 있다.
기존 인터페이스에 디폴트 메서드로 새 메서드를 추가하는 일은 꼭 필요한 경우가 아니면 피하자.
반면, 새로운 인터페이스를 만드는 경우라면 표준적인 메서드 구현을 제공하는데 아주 유용한 수단이 된다.
또한 그 인터페이스를 더 쉽게 구현해 활용할 수 있게끔 해준다. (Item 20)
디폴트 메서드는 인터페이스로부터 메서드를 제거하거나 기존 메서드의 시그니처를 수정하는 용도가 아님을 명심하자.
💡 인터페이스를 설계할 때는 여전히 세심한 주의를 기울여야 한다.
새로운 인터페이스라면 릴리즈 전에 반드시 테스트를 거쳐야 한다.
적어도 서로 다른 방식으로 세 가지는 구현해보자.
각 인터페이스의 인스턴스를 다양한 작업에 활용하는 클라이언트도 여러 개 만들어 봐야 한다.
인터페이스를 릴리스한 후라도 결함을 수정하는 게 가능한 경우도 있겠지만, 절대 그 가능성에 기대지 마라.
'Reference > Effective-Java' 카테고리의 다른 글
[Effective-Java] Chapter4 #23. 태그 달린 클래스보다는 클래스 계층구조를 활용하라 (0) | 2023.04.30 |
---|---|
[Effective-Java] Chapter4 #22. 인터페이스는 타입을 정의하는 용도로만 사용하라 (0) | 2023.04.30 |
[Effective-Java] Chapter4 #20. 추상 클래스보다는 인터페이스를 우선하라 (0) | 2023.04.30 |
[Effective-Java] Chapter4 #19. 상속을 고려해 설계하고 문서화하라. 그러지 않았다면 상속을 금지하라 (0) | 2023.04.30 |
[Effective-Java] Chapter4 #18. 상속보다는 컴포지션을 사용하라 (0) | 2023.03.25 |