인터페이스에서 새로운 메소드를 추가하면 기존의 구현체들에 영향이 가게 된다. 이러한 문제를 해결하여 새로운 메소드를 추가할 수 있도록 한 것이 자바 8 부터 등장한 default 메소드이다.
default 메소드
1
2
3
4
5
6
7
public interface JavaStudyable { // ~able 네이밍 규칙을 가진다.
public abstract void study(); //일반적인 메서드 선언
public default void reset(){ //default 메서드 선언 후 구현
System.out.println("쉽시다아");
}
}
default 메서드를 사용함으로써 구현체에 영향을 주지 않고 처음 설계를 자유롭게 변경하면 좋겠지만, 책에서는 default 메서드를 남발하지 말라고 되어있다.
default 메서드들이 어떻게 사용되는지 알아보고 default 메서드를 지양하는 이유를 알아보자.
default 메소드 사용 예시
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public interface JavaStudyable { // ~able 네이밍 규칙을 가진다.
public abstract void study(); //일반적인 메서드 선언
public default void reset(){ //default 메서드 선언 후 구현
System.out.println("쉽시다아");
}
}
class RealTest implements JavaStudyable{
@Override
public void study(){
System.out.println("공부합시당");
}
public static void main(String[] args) {
RealTest rt = new RealTest();
rt.study();
rt.reset();
}
}
default 메소드인 reset을 따로 RealTest class에서 구현하지 않았지만 오류없이 해당 메소드가 실행된다.
인터페이스에 새로운 default 메서드가 추가되어도 새로운 default 메서드를 구현하지 않은 기존 구현체에 영향을 주지 않는 다는 것을 알 수 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public interface JavaStudyable { // ~able 네이밍 규칙을 가진다.
public abstract void study(); //일반적인 메서드 선언
public default void reset(){ //default 메서드 선언 후 구현
System.out.println("쉽시다아");
}
}
class RealTest implements JavaStudyable{
@Override
public void study(){
System.out.println("공부합시당");
}
public static void main(String[] args) {
RealTest rt = new RealTest();
rt.study();
rt.reset();
}
}
1
2
공부합시당
신나는 15분의 휴식시간!
default 메서드를 Override하여 재정의하는 것도 가능하다.
default 메소드가 추가된 인터페이스를 사용 시 문제점
1. 구현체는 default 메서드가 추가 되었는지 모른다.
해당 인터페이스를 가져다 쓰는 개발자는 Override 하지 않아도 실행되는 default 메서드에 의해 의도하지 않은 결과를 얻을 수 있다.
2. 문제점이 런타임 시에 발견될 수 있다.
default가 아닌 메소드는 컴파일 시점에 잘못 구현됨을 알 수 있지만 default 메소드는 런타임시에 알아차릴 수 있다.
3. 모든 상황에서 불변식을 해치지 않는다고 보장할 수 없다.
자바 7까지는 “현재의 인터페이스에 새로운 메소드는 추가될 일은 영원히 없다”라는 가정하에 개발되어 왔다.
ex) 자바 8 Collection 인터페이스에 추가된 default removeIf 메소드
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public interface Collection<E> extends Iterable<E> {
...
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;
}
...
}
org.apache.commons.collections4.collection.SynchronizedCollection는 클라이언트가 제공한 객체에 lock
을 거는 능력을 추가하여 스레드간 동기화를 보장하는 클래스다.
하지만 4.3 버전 이하에서는 removeIf 메소드가 Override 되질 않고 default 형태의 removeIf 메소드가 그대로 사용되어 동기화가 이루어질 않는다. 멀티스레드 환경에서 해당 메소드를 사용하게 되면 ConcurrentModificationException 이 발생하거나 예기치 못한 결과를 얻을 수 있다.
이를 해결하기 위해서는 아래와 같이 default removeIf 호출전 동기화 작업을 해주어야 한다(자바 플랫폼 라이브러리가 이렇게 조치를 취함)
1
2
3
4
5
6
7
8
public class Collections {
static class SynchronizedCollection<E> implements Collection<E>, Serializable {
@Override
public boolean removeIf(Predicate<? super E> filter) {
synchronized (mutex) {return c.removeIf(filter);}
}
}
}