본문 바로가기

STUDY/이펙티브자바

4-7) 인터페이스는 구현하는 쪽을 생각해 설계하라

인터페이스와 디폴트 메소드

 

// MyInterface.java
// MyInterface의 구현체인 MyClass가 존재하기 때문에 MyInterfece에 메소드를 추가하면 컴파일 에러가 발생한다.
public interface MyInterface {
    public void method1();
}

// MyClass.java
public class MyClass implements MyInterface {
    @Override
    public void method1() {
    }
}

인터페이스의 구현체가 한번 정의되고 나면 인터페이스에 메소드를 새로 추가하기 어렵다.  구현체 클래스는 개발당시의 인터페이스 메소드들을 구현했기 때문에, 새로 추가된 메소드는 미구현 상태가 되고 컴파일 에러가 발생한다. 따라서 인터페이스는 거의 바뀌지 않는다는 가정하에 구현체를 만들었고 인터페이스가 바뀌면 그 구현체들은 모두 이에 대응해야 했다. 하지만 Java 8 부터는 디폴트 메소드 덕분에 구현체의 수정 없이도 인터페이스에 새로운 메소드를 추가할 수 있다.

 

// default 키워드를 통해 인터페이스에서 몸통(구현)이 있는 메소드를 추가하였다.
public interface MyInterface {
    public void method1();
	
    default public void method2() {
        System.out.println("기본 구현");
    }
}

원래 인터페이스는 메소드의 구현 내용이 담긴 몸통을 정의할 수 없었다. 그래서 인터페이스를 상속하는 클래스는 미구현된 메소드들을 의무적으로 구현해야 했다. 하지만 Java 8 부터는 default 키워드를 사용하면 인터페이스의 메소드에도 구현내용이 담김 몸통을 정의할 수 있다. 그 이름처럼 default 메소드를 구현체 클래스에서 재정의 하지 않으면 기본 구현 내용이 동작한다.

 

 


구현체 클래스 개발자는 default 메소드 추가에 속수무책이다

 

// Java 8 부터 Collection 인터페이스에 removeIf 메소드가 추가되었다.
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;
    }
    ....
}

Java 8부터 Collection 인터페이스에 removeIf 메소드가 추가되었다. Collection 인터페이스는 자바의 여러곳에서 구현되어 사용되고 있었기 때문에 default 메소드가 없었다면 수많은 구현체들이 컴파일 에러가 발생하고 코드를 수정해야 했을 것이다. default 메소드를 통해 기본 동작 코드를 정의해두었기 때문에 큰 문제 없이 인터페이스에 메소드를 새로 추가할 수 있었다.

 

List<Integer> list = new ArrayList<>();
for(Integer i = 0; i < 10000000; i++) {
	list.add(i);
}
Collection<Integer> col = SynchronizedCollection.synchronizedCollection(list);

// Collection의 구현체인 SynchronizedCollection 객체를 만들고
// t1, t2에서 동시에 removeIf 메소드를 호출한다.
Thread t1 = new Thread(new Test(col));
Thread t2 = new Thread(new Test(col));
t1.start();
t2.start();

 

Exception in thread "Thread-1" java.util.ConcurrentModificationException
...

SynchronizedCollection는 멀티스레드 환경에서 사용가능한 Collection 구현체이다. 멀티스레드에서 안전하게 동작하도록 만들어진 클래스인데 멀티스레드에서 removeIf를 호출하였더니 ConcurrentModificationException 예외가 발생하였다. SynchronizedCollection 클래스 개발자의 잘못일까? 아니다. SynchronizedCollection 클래스를 만들 당시에는 Collection 인터페이스에 removeIf 메소드는 없었다. 이후에 SynchronizedCollection 클래스의 개발자도 모르게 removeIf 메소드가 추가된 것이다. 이처럼 default 메소드를 인터페이스에 추가할 때는 구현체 클래스에 의도치 않은 문제가 생길 수 있음을 고려해야한다.

 

728x90