자바가 제공하는 다중 구현 메커니즘에는 인터페이스와 추상 클래스가 있다. 자바 8부터는 인터페이스도 디폴트 메서드를 제공할 수 있게 되어 인터페이스와 추상 클래스 모두 인스턴스 메서드를 구현 형태로 제공할 수 있다.
둘의 가장 큰 차이는 추상 클래스가 정의한 타입을 구현하는 클래스는 반드시 추상 클래스의 하위 클래스가 되어야 한다. 자바는 단일 상속만 지원하니 큰 제약이다. 반면에 인터페이스는 선언한 메서드를 모두 정의하고 그 규약을 지켜 구현한 클래스라면 다른 어떤 클래스를 상속해도 같은 타입으로 취급한다.
인터페이스가 요구하는 메서드를 추가하고 클래스 선언에 implements 구문만 추가하면 끝이다.
실제로 Comparable
, Iterable
, AutoCloseable
등 새로운 인터페이스를 수 많은 기존 클래스가 이 인터페이스를 구현한 채 릴리즈됐다.
하지만 기존 클래스에 새로운 추상 클래스를 끼워 넣기는 어렵다. 두 클래스가 같은 추상 클래스를 확장하려면, 그 추상 클래스는 계층 구조상 두 클래스의 공통 조상이어야 한다. 큰 혼란을 야기할 수 있다.
믹스인 클래스가 구현할 수 있는 타입으로 클래스의 원래 ‘주된 타입’외에도 다른 특정 선택적 행위를 제공한다고 선언하는 효과를 준다.
Comperable
은 자신을 구현한 클래스의 인스턴스끼리는 순서를 정할 수 있다고 선언하는 믹스인 인터페이스이다. 이처럼 대상 타입의 주된 기능에 선택적 기능을 ‘혼합(mixed in)’한다고 해서 믹스인이라 부른다. 추상 클래스로는 믹스인을 정의할 수 없다. 이유는 앞서 이야기한 것과 같이, 기존 클래스에 덧씌울 수 없기 때문이다. 클래스는 두 부모를 섬길 수 없고, 클래스 계층구조에는 믹스인을 삽입하기에 합리적인 위치가 없기 때문이다.
타입을 계층적으로 정의하면 수 많은 개념을 구조적으로 잘 표현할 수 있지만, 현실에는 계층을 엄격히 구분하기 어려운 개념도 있다. 예시로 살펴보자-
public interface Singer {
AudioClip sing(Song s);
}
public interface Songwriter {
Song compose(int chartPosition);
}
만약 작곡도 하고 노래도 한다면 두 인터페이스 모두를 구현해도 된다. 심지어 모두를 extends하고 제 3의 메서드도 추가 가능하다.
public interface SingerSongwriter extends Singer, Songwriter {
AudioClp strum();
void actSensitive();
}
이 정도의 유연성이 항상 필요치는 않지만, 이렇게 만들어둔 인터페이스가 결정적인 도움을 줄 수도 있다. 같은 구조를 클래스로 만들려면 속성이 n개라면 지원해야 할 조합의 수는 2^n개나 된다. 흔히 조합 폭발이라 부르는 현상이다.
타입을 추상 클래스로 정의해두면 그 타입에 기능을 추가하는 방법은 상속뿐이다. 상속해서 만든 클래스는 래퍼 클래스보다 활용도가 떨어지고 깨지기는 더 쉽다.
인터페이스의 메서드 중 구현 방법이 명백한 것이 있다면, 디폴트 메서드로 만들 수 있다. 그러나 디폴트 메서드는 제약이 있다.