추상 클래스와 인터페이스는 모두 인스턴스 메소드를 구현 형태로 제공할 수 있다.
하지만 추상 클래스를 구현하는 클래스는 반드시 하위 클래스가 되어야 한다는 것이다. 자바에서는 단일 상속만 지원하기 때문에 엄청난 부담이 될 것이다.
인터페이스의 장점을 알아보자.
인터페이스와 추상 골격 구현(skeletal implementation) 클래스를 함께 제공하여 인터페이스와 추상 클래스의 장점을 모두 취하는 방법도 있다. 템플릿 메소드 패턴
이다.
static List<Integer> intArrayAsList(int[] a) {
Objects.requireNonNull(a);
return new AbstractList<>() {
@Override
public Integer get(int i) {
return a[i]; // Autoboxing (Item 6)
}
@Override
public Integer set(int i, Integer val) {
int oldVal = a[i];
a[i] = val; // Auto-unboxing
return oldVal; // Autoboxing
}
@Override
public int size() {
return a.length;
}
};
}
관례상 이름 앞에 Abstract를 붙인다. Skeletal이 더 적절했을지는 모르지만 말이다.
이 예시는 int 배열을 받아 Integer 인스턴스의 리스트 형태로 보여주는 어댑터(Adapter) 이다.
오토박싱, 언박싱이 일어나서 성능상으로는 그렇게 좋지는 않다.
골격 구현 클래스는 추상 클래스처럼 구현을 도와주지만 타입 정의 시의 제약에서 자유롭다. 하지만 추상 클래스는 타입을 그대로 따라야 한다는 제약이 있다.
하지만 이런 식으로 무조건 골격 구현 클래스를 만들어야 하는 것은 아니다. 구조상 골격 구현을 하지 못할 때는 인터페이스를 직접 구현해야 할 것이다. 하지만 이 때는 인터페이스의 디폴트 메소드의 이점을 살릴 수가 있다. 내부에서 private 클래스를 정의하고 메소드 호출을 내부 클래스 인스턴스에 넘겨주는 식으로 말이다.
이것을 시뮬레이트한 다중 상속(simulated multiple inheritance)이라고 한다.
골격 구현 작성은 인터페이스의 기반 메소드를 구현한다. 그리고 만약 인터페이스의 모든 메소드가 기반 메소드라면 골격 구현 클래스를 만들 필요가 없다!
골격 구현은 기본적으로 상속을 가정하므로 아이템19에서 나왔던 것처럼 문서를 남겨야 한다.
단순 구현(simple implementation)은 골격 구현의 작은 변종이다.
핵심 정리
다중 구현을 할 때는 인터페이스의 장점이 많다. 추상 클래스는 인터페이스와 함께 쓰여 템플릿 메소드 패턴으로 쓰인다. 골격 구현은 가능한 한 인터페이스의 디폴트 메소드로 제공하여 구현한 모든 곳에서 사용할 수 있도록 하는 것이 좋다.