이 장의 내용
•
디폴트 메서드란 무엇인가?
•
진화하는 API가 호환성을 유지하는 방법
•
디폴트 메서드의 활용 패턴
•
해결 규칙
•
라이브러리 설계자 입장에서 인터페이스에 메서드를 추가하거나 변경하는 경우, 무제가 발생한다.
•
인터페이스를 구현했던 모든 클래스의 구현도 바꿔야 하기 때문이다.
•
자바 8에서는 이 문제를 해결하고자 기본 구현을 포함하는 인터페이스를 정의하는 두 가지 방법을 제공한다.
◦
정적 메서드를 사용하는 것이다.
◦
기본 구현을 제공할 수 있도록 디폴트 메서드를 사용하는 것이다.
•
디폴트 메서드는 라이브러리 설계자들이 주로 사용한다. 이를 사용하면 자바 API의 기존 호환성을 유지하면서 라이브러리를 수정할 수 있기 때문이다.
1. 변화하는 API
•
이미 릴리즈된 인터페이스를 고치면 많은 문제가 발생한다.
•
사용자들이 해당 인터페이스를 구현하는 클래스를 만들었다면 인터페이스에 수정이 발생했을 때, 해당 클래스들이 모두 정상적으로 동작하지 않게 되어버린다.
•
이렇게 공개된 API에 변화가 발생하면 기존 버전과의 호환성 문제가 발생한다.
•
이를 해결하기 위해 자바 8에서는 디폴트 메서드를 제공하고 있다.
2. 디폴트 메서드란 무엇인가?
•
자바 8에서 호환성을 유지하면서 API를 변경할 수 있도록 지원하는 새로운 기능이다.
•
인터페이스는 자신을 구현하는 클래스에서 메서드를 구현하지 않아도 되는 새로운 메서드 시그니처를 제공한다.
•
디폴트 메서드는 인터페이스를 구상 클래스에서 구현하지 않은 메서드는 인터페이스에서 기본적으로 제공한다.
•
default라는 키워드로 시작하며 다른 클래스에 선언된 메서드처럼 메서드 바디를 포함한다.
•
추상 클래스 vs 자바 8의 인터페이스
◦
추상 클래스와 인터페이스는 뭐가 다를까?
1.
클래스는 하나의 추상 클래스만 상속받을 수 있지만 인터페이스는 여러 개 구현할 수 있다.
2.
추상 클래스는 인스턴스 변수로 공통 상태를 가질 수 있다. 하지만 인터페이스는 인스턴스 변수를 가질 수 없다.
3. 디폴트 메서드 활용 패턴
1.
선택형 메서드
•
자바 8 이전에는 Iterator 인터페이스의 remove 메소드는 잘 사용하지 않아 무시되었기 때문에 많은 구상 클래스들이 빈 구현을 제공했다.
•
디폴트 메서드를 이용하면 remove같이 중요도가 낮은 메서드에 기본 구현을 제공할 수 있으므로 구상 클래스에서 굳이 빈 구현을 제공하지 않아도 된다.
•
기본 구현이 제공되므로 구상 클래스는 빈 구현을 유지할 필요가 없어졌고 불필요한 코드를 줄일 수 있다.
2.
동작 다중 상속
•
디폴트 메서드를 이용하면 기존에는 불가능했던 동작 다중 상속 기능도 구현할 수 있다.
•
자바에서 클래스는 한 개의 클래스만 상속할 수 있지만 인터페이스는 여러 개 구현할 수 있기 때문이다.
•
다음 ArrayList 클래스의 예시를 봐보자.
public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, Serializable { ... }
Java
복사
◦
여기서 ArrayList는 한 개의 클래스를 상속받고 여섯 개의 인터페이스를 구현한다.
◦
결과적으로는 AbstaractList, List, RandomAccess, Cloneable, Serializable, Iterable, Collection의 자손 타입이 되는 것이다.
◦
따라서 디폴트 메서드를 사용하지 않아도 다중 상속을 활용할 수 있다.
•
다중 상속을 중복되지 않는 최소한의 인터페이스로 유지한다면 우리는 코드에서 동작을 쉽게 재사용하고 조합할 수 있다.
4. 해석 규칙
•
자바의 클래스는 하나의 부모 클래스만 상속받을 수 있지만 여러 인터페이스를 동시에 구현할 수 있다.
•
자바 8에는 디폴트 메서드가 추가되었으므로 같은 시그니처를 갖는 디폴트 메서드를 상속받는 상황이 생길 수 있을텐데 이런 상황에서는 어떻게 메서드를 선택할까?
1.
알아야 할 세가지 해결 규칙
•
클래스가 항상 이긴다. 클래스나 슈퍼 클래스에서 정의한 메서드가 디폴트 메서드보다 우선권을 갖는다.
•
1번 규칙 이외의 상황에서는 서브 인터페이스가 이긴다. 상속 관계를 갖는 인터페이스에서 같은 시그니처를 갖는 메서드를 정의할 때는 서브 인터페이스가 이긴다.
◦
즉 B가 A를 상속받는다면 B가 A를 이긴다.
•
여전히 디폴트 메서드의 우선순위가 결정되지 않았다면 여러 인터페이스를 상속받는 클래스가 명시적으로 디폴트 메서드를 오버라이드하고 호출해야 한다.
2.
디폴트 메서드를 제공하는 서브 인터페이스가 이긴다.
•
A와 B 인터페이스가 존재하고 B가 A를 상속받는 상황에서 A, B를 구현하는 클래스 C가 있다고 해보자.
•
A, B 인터페이스 모두 hello()라는 메소드가 존재한다면 컴파일러는 누구의 정의를 사용할까? 여기선 클래스가 없으니 서브 인터페이스인 B가 이길 것이라고 설명한다.
5. 마치며
•
자바 8의 인터페이스는 구현 코드를 포함하는 디폴트 메서드, 정적 메서드를 정의할 수 있다.
•
디폴트 메서드의 정의는 default 키워드로 시작하며 일반 클래스 메서드처럼 바디를 갖는다.
•
공개된 인터페이스에 추상 메서드를 추가하면 소스 호환성이 깨진다.
•
디폴트 메소드 덕분에 라이브러리 설계자가 API를 바꿔도 기존 버전과의 호환성을 유지한다.
•
선택형 메서드와 동작 다중 상속에도 디폴트 메소드를 사용할 수 있다.
•
클래스가 같은 시그니처를 갖는 여러 디폴트 메서드를 상속하면서 생기는 충돌 문제를 해결하는 규칙이 있다.
•
클래스나 슈퍼 클래스에 정의된 메서드가 다른 디폴트 메서드 정의보다 우선한다. 이 외의 상황에서는 서브 인터페이스에서 제공하는 디폴트 메서드가 선택된다.
•
두 메서드의 시그니처가 같고 상속관계로도 충돌 문제를 해결할 수 없을 때에는 디폴트 메서드를 사용하는 클래스에서 메서드를 오버라이드 해서 어떤 디폴트 메서드를 호출할지 명시해주어야 한다.