자바 8 이전에는 기존 구현체를 깨뜨리지 않고는 인터페이스에 메서드를 추가할 방법이 없었지만, 자바 8부터는 디폴트 메소드 를 통해 인터페이스에 메서드를 추가하는 것이 가능해졌다.
기존에 존재하던 구현체들과 연동되지 않을 수 있다.
인터페이스를 구현한 후 디폴트 메서드를 재정의하지 않은 '모든' 클래스에서도 디폴트 구현이 쓰이게 되기 때문이다.
자바 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/