자바 8에서는 메서드 구현을 포함하는 인터페이스를 정의 할 수 있다.
-> 결과적으로 기존 인터페이스를 구현하는 클래스는 자동으로 인터페이스에 추가된 새로운 메서드의 디폴트 메서드를 상속받게 된다.
(ex. List
인터페이스의 sort
메서드, Collection
인터페이스의 stream
메서드)
List<Integer> numbers = Arrays.asList(3, 5, 1, 2, 6);
numbers.sort(Comparator.naturalOrder());
//-> sort는 List 인터페이스의 디폴트 메서드이다.
- 디폴트 메서드는 주로 라이브러리 설계자들이 사용.
- 디폴트 메서드를 이용하면 자바 API의 호환성을 유지하면서 라이브러리를 바꿀 수 있다.
- 디폴트 메서드를 이용하면 인터페이스의 기본 구현을 그대로 상속하므로 인터페이스에 자유롭게 새로운 메서드를 추가할 수 있게 된다.
- 디폴트 메서드는 다중 상속 동작이라는 유연성을 제공하면서 프로그램 구성에도 도움을 준다.
아래와 같은 Resizable 인터페이스의 초기 버전이 있다는 가정하에
public interface Resizable extends Drawable {
int getWidth();
int getHeight();
void setWidth(int width);
void setHeight(int height);
void setAbsoluteSize(int width, int height);
}
추후에 Resizable을 구현하는 Square와 Rectangle 구현을 개선하는 요청을 받고 메서드를 아래와 같이 매서드를 추가한다면 몇 가지 문제가 발생한다.
public interface Resizable extends Drawable {
int getWidth();
int getHeight();
void setWidth(int width);
void setHeight(int height);
void setAbsoluteSize(int width, int height);
void setAbsoluteSize(int width, int height);
void setRelativeSize(int widthFactor, int heightFactor);
}
발생되는 문제점
1. Resizable을 구현하는 모든 클래스는 setRelativeSize 메서드를 구현해야 한다.
2. 인터페이스에 새로운 메서드를 추가하면 바이너리 호환성은 유지된다. 바이너리 호환성이란 새로 추가된 메서드를 호출하지만 않으면 새로운 메서드 구현이 없이도 기존 클래스 파일 구현이 잘 동작한다는 의미다. 하지만, Resizable을 인수로 받는 클래스에서 추가된 메서드를 사용하도록 코드를 바꾸게 되면 정의되지 않은 메서드에 대해서 런타임 에러가 발생한다.
3. 재빌드시 컴파일 에러가 발생한다.
// 2번에 대한 예제
// 1. 사용자가 Resizable 인터페이스를 상속받아 구현했다.
// 2. Resizable 인터페이스에 추가된 'setRelativeSize' 메서드에 대해서 추가 구현하지 않았다.
public class Ellipse implements Resizable {
@Override
public int getWidth() {
return 0;
}
@Override
public int getHeight() {
return 0;
}
@Override
public void setWidth(int width) {
}
@Override
public void setHeight(int height) {
}
@Override
public void setAbsoluteSize(int width, int height) {
}
@Override
public void draw() {
}
}
public class Game {
public static void main(String... args) {
List<Resizable> resizableShapes = Arrays.asList(
new Square(), new Triangle(), new Ellipse());
// 객체를 아래 메서드로 전달
Utils.paint(resizableShapes);
}
}
public class Utils {
public static void paint(List<Resizable> l) {
l.forEach(r -> {
r.setAbsoluteSize(42, 42);
});
// Ellipse 클래스에서 아래 setRelativeSize를 구현하지 않았기 때문에 해당 구문에서 런타임 에러가 발생한다.
l.forEach(r -> { r.setRelativeSize(2, 2); });
}
}
Exception in thread "main" java.lang.AbstractMethodError
: lambdasinaction.chap9.Ellipse.setRelativeSize(II)V
발생되는 문제점
4. 공개된 API를 고치면 기존 버전과의 호환성 문제가 발생한다. 이런 이유 때문에 공식 자바 컬렉션 API같은 기존의 API는 수정이 어렵다.
5. 자신만의 API를 별도로 만들어 새로운 버전을 직접 관리하는 방법도 있지만, 이는 여러 가지로 불편하다. 이유는 첫째, 라이브러리를 관리하기가 복잡하고 둘째, 사용자는 같은 코드에 이전 버전과 새로운 버전 두가지 라이브러리를 모두 사용해야 하는 상황이 생긴다. 이는 프로젝트에 로딩해야 할 클래스 파일이 많아지면서 메모리 사용과 로딩 시간 문제가 발생한다.
디폴트 메서드를 이용해서 APi를 바꾸면 새롭게 바뀐 인터페이스에서 자동으로 기본 구현을 제공하기 때문에 기존 코드를 고치치 않아도 된다.
출처 : 모던 자바 인 액션