디폴트 메서드가 도입되기 전 확장성에 대한 문제점
전통적인 자바에서 인터페이스와 관련 메서드는 한 몸처럼 구성된다. 인터페이스를 구현하는 클래스는 인터페이스에서 정의하는 모든 메서드 구현을 제공하거나 아니면 슈퍼클래스의 구현을 상속받아야 한다.
- 구현 클래스에는 인터페이스 추상 메서드 구현이 필수이다.
평소에는 이 규칙을 지키는 데 아무 문제가 없지만 라이브러리 설계자 입장에서는 인터페이스에 새로운 메서드를 추가하는 등 인터페이스를 바꾸고 싶을 때는 문제가 발생한다. 인터페이스를 바꾸면 이전에 해당 인터페이스를 구현했던 모든 클래스의 구현도 고쳐야 하기 때문이다.
- 자바 8 API에도 List 인터페이스에 sort 같은 메서드를 추가했으므로 문제가 발생할 수 있다. 컬렉션 프레임워크인 Guava, Apache Common 등을 포함해서 기존의 List 인터페이스를 구현했던 모든 프로그래머가 sort 메서드를 구현하도록 List 인터페이스를 상속한 모든 클래스를 고쳐야 한다고 발표한다면 커뮤니티는 꽤 크게 당황할 것이다.
인터페이스의 디폴트 메서드 (Default Method)
그래서 자바 8에서 인터페이스 내부에 static 메서드와 default 메서드를 사용할 수 있도록 새롭게 제공했다. 디폴트 메서드를 이용하면 인터페이스의 기본 구현을 그대로 상속하므로 인터페이스에 자유롭게 새로운 메서드를 추가할 수 있게 된다. 그래서 디폴트 메서드는 주로 라이브러리 설계자들이 사용한다. 왜냐하면 디폴트 메서드를 이용하면 자바 API의 호환성을 유지하면서 라이브러리를 바꿀 수 있기 때문이다.
디폴트 메서드 장점
- 기존 구현을 고치지 않고도 인터페이스를 바꿀 수 있으므로 자바 API의 호환성을 유지와 함께 확장에 용이하다.
- 디폴트 메서드는 다중 상속 동작이라는 유연성을 제공하면서 프로그램 구성에도 도움을 준다.
예로, List 인터페이스의 sort 메서드가 자바 8에 새롭게 추가된 디폴트 메서드이다.
default void sort(Comparator<? super E> c){
Collections.sort(this.c);
}
디폴트 메서드 활용 패턴
1. 선택형 메서드
디폴트 메서드는 구현 클래스에서 직접 구현하지 않아도 오버라이딩이 되므로 굳이 구현해주지 않아도 된다. 즉 구현 클래스에서는 선택적으로 디폴트 메서드를 사용할 수 있다.
interface Iterator<T> {
boolean hasNext();
T next();
default void remove() {
throw new UnsupportedOperationException();
}
}
2. 동작 다중 상속
인터페이스는 다중 상속이 가능하므로 클래스는 여러 인터페이스에서 동작을 상속 받을 수 있다. 만약 기능이 중복될 경우 결합력이 가장 높은 기능을 선택한다. 이는 아래의 해석 규칙을 참고하자.
class Horse{
public String identityMySelf(){
return "I am a horse";
}
}
interface Flyer {
default public String identityMySelf(){
return "I am able to fly";
}
}
interface Mythical {
default public String identityMySelf(){
return "I am a mythical creature";
}
}
public class Pegasus extends Horse implements Flyer, Mythical {
public static void main(String[] args) {
Pegasus myApp = new Pegasus();
System.out.println(myApp.identityMySelf()); // "I am a horse"
}
}
Pegasus클래스는 결합력이 가장 높은 Horse 클래스의 메서드를 오버라이딩하여 사용한다.
해석 규칙
다른 클래스나 인터페이스로부터 같은 시그니처를 갖는 메서드를 상속받을 떄는 세 가지 규칙을 따라야 한다. 이는 결합력의 차이이다.
- 클래스가 항상 이긴다. 클래스나 슈퍼클래스에서 정의한 메서드가 디폴트 메서드보다 우선권을 갖는다.
- 1번 규칙 이외의 상황에서는 서브 인터페이스가 이긴다. 상속관계를 갖는 인터페이스에서 같은 시그니처를 갖는 메서드를 정의할 때는 서브 인터페이스가 이긴다. 즉, B가 A를 상속받는다면 B가 A를 이긴다.
- 여전히 디폴트 메서드의 우선순위가 결정되지 않았다면 여러 인터페이스를 상속받는 클래스가 명시적으로 디폴트 메서드를 오버라이드라고 호출해야 한다.
사실 이 규칙은 오버라이딩, 하이딩, 클래스와 인터페이스 결합력 차이에 대해 이해한다면 따로 외우지 않아도 된다.
- 추상 클래스와 인터페이스의 차이는 결합력이다. 클래스가 인터페이스도 결합력이 더 높다.
- 런타임에 결정되는 다형성의 특징 오버라이딩도 결합력에 의해 결정된다. 해당 구현 클래스와 가장 높은 결합력을 가진 기능을 사용한다. 메서드의 오버라이딩과 하이딩에 대해 공부하면 이해하기 쉽다.
참고
'Dot Programming > Java' 카테고리의 다른 글
[Java] 자바 서블릿과 서블릿 컨테이너 2 - MVC 패턴 도입 (0) | 2022.02.10 |
---|---|
[Java] 자바 서블릿과 서블릿 컨테이너 1 - 웹 서버 한계와 WAS 등장 (0) | 2022.02.10 |
[Java] 메서드 오버라이딩과 하이딩에 대해 (Overriding and Hiding Methods) (0) | 2022.01.31 |
[Java] 추상 클래스 vs 인터페이스 (추상 클래스와 인터페이스의 차이는 결합력) (0) | 2022.01.31 |
2022년 자바 개발자 로드맵 (The 2022 Java Programmer RoadMap) (0) | 2022.01.17 |