자바 8 이전에는 기존 구현체를 깨뜨리지 않고는 인터페이스에 메서드를 추가할 방법이 없었다.
자바 8에서 디폴트 메서드를 추가할 수 있도록 변경되었지만 이를 믿고 그대로 사용하는 것은 위험하다.
만약 인터페이스에 디폴트 메서드를 선언한다면 디폴트 메서드를 재정의하지 않은 해당 인터페이스를 구현한 모든 클래스에서 디폴트 구현이 쓰이게 된다.
이 부분이 주의해야 할 점이라고 한다.
그 이유는 매끄럽게 연동되리라는 보장이 없기 때문이다.
자바 7까지는 "현재의 인터페이스에 새로운 메서드가 추가될 일은 영원히 없다"고 가정하고 작성되었다.
이러한 디폴트 메서드는 구현 클래스를 고려하지 않고 무작정 '삽입'만 될 뿐이다.
자바 8에서는 핵심 컬렉션 인터페이스들에 다수의 디폴트 메서드가 추가되었다.
주로 람다를 참조하기 위해서라고 한다.
자바 라이브러리의 디폴트 메서드는 물론 코드 품질이 높고 범용적이라 대부분 상황에서 잘 작동한다.
하지만 모든 상황에서 불변식을 해치치 않는 디폴트 메서드를 작성하기는 어렵다.
하나의 예를 보자.
자바 8에서 Collection 인터페이스에 추가된 removeIf가 있다.
이 메서드는 반복자를 이용해 순회하면서 remove 메서드를 호출해 그 원소를 제거한다.
해당 디폴트 메서드와 매끄럽게 연동되지 않는 클래스가 있다.
바로 org.apache.commons.collections4.collection.SynchronizedCollection이다.
단순하게 Collections.synchronizedCollection 클래스와 비슷한데 아파치 버전은 (컬렉션 대신) 클라이언트가 제공한 객체로 락을 거는 능력을 추가로 제공한다.
아파치의 SynchronizedCollection 클래스는 이 책이 쓰여진 시점엔 removeIf 메서드를 재정의하지 않고 있다.
따라서 자바 8과 함께 사용한다면 removeIf의 디폴트 구현을 물려받게 되고 그로 인해 동기화 기능이 정상적으로 동작하지 않는다.
removeIf는 동기화에 관해 아무것도 모르기 때문에 락 객체를 사용할 수 없는 것이다.
때문에 SynchronizedCollection 인스턴스를 여러 스레드가 공유하는 환경에서 한 스레드가 removeIf를 호출하면 ConcurrentModificationException이 발생하거나 다른 예기치 못한 결과로 이어질 수 있다.
자바 플랫폼 라이브러리에서 문제 예방을 위해 일련의 조치를 취했다.
예를 들면 Collections.SynchronizedCollection이 반환하는 package-private 클래스들은 removeIf를 재정의하고, 이를 호출하는 다른 메서드들은 디폴트 구현을 호출하기 전에 동기화 하도록 하는 것이다.
하지만 자바 플랫폼에 속하지 않은 제 3의 기존 컬렉션 구현체들 중 일부는 수정될 기회가 없었고 여전히 수정되지 않고 있다.
디폴트 메서드는 (컴파일에 성공하더라도) 기존 구현체에 런타임 오류를 일으킬 수 있다.
자바 8은 컬렉션 인터페이스에 꽤 많은 디폴트 메서드를 추가했고, 그 결과 기존에 짜여진 많은 자바 코드가 영향을 받은 것으로 알려졌다.
기존 인터페이스에 디폴트 메서드로 새 메서드를 추가하는 일은 꼭 필요한 경우가 아니면 피해야 한다.
추가하려는 디폴트 메서드가 기존 구현체들과 충돌하지는 않을지도 심사숙고해야 한다.
반면에 새로운 인터페이스를 만드는 경우라면 표준적인 메서드 구현을 제공하는데에 아주 유용한 수단이며, 인터페이스를 더 쉽게 구현해 활용할 수 있게끔 해준다.
디폴트 메서드라는 도구가 생겼더라도 인터페이스를 설계할 때는 세심한 주의를 기울여야 한다.
'Book > Effective Java' 카테고리의 다른 글
[Effective Java]23.태그 달린 클래스보다 클래스 계층구조를 활용하라 (0) | 2022.07.06 |
---|---|
[Effective Java]22.인터페이스는 타입을 정의하는 용도로만 사용해라 (0) | 2022.07.06 |
[Effective Java]20.추상 클래스보다는 인터페이스를 우선하라 (0) | 2022.07.02 |
[Effective Java]19.상속을 고려해 설계하고 문서화하라 (0) | 2022.06.27 |
[Effective Java]18.상속보다는 컴포지션을 사용하라 (0) | 2022.06.10 |