Java8 In Action 을 읽고 정리한 내용이다. 디폴트 메서드란 무엇이며 API가 바뀌면서 발생한 문제를 디폴트 메서드로 어떻게 해결할 수 있는 설명한다. 그리고 디폴트 메서드를 만들어 다중 상속을 달성하는 방법을 보여준다.
변화하는 API
우리가 인기 있는 자바 그리기 라이브러리 설계자가 되었다고 가정한다.
- 모양의 크기를 조정하는 데 필요한
setHeigh
,setWidth
,getHeight
,getWidth
,setAbsoluteSize
등의 메서드를 정의하는Resizable
인터페이스 생성 Rectagle
이나,Square
처럼Resizable
을 구현하는 클래스 제공- 일부 사용자가 직접
Resizable
인터페이스를 구현하는Ellipse
라는 클래스 구현 - API를 릴리즈한지 몇개월이 지나면서
Resizable
에 몇 가지 기능이 부족하여 크기 조절 인수로 모양의 크기를 조절할 수 있는setRelativeSize
라는 메서드를 추가 Resizable
에setRelativeSize
를 추가하고Square
와Rectangle
구현을 수정
문제점 : 바로 자바 라이브러리 설계자가 라이브러리를 바꾸고 싶을 때 같은 문제가 발생
API 버전 1
Resizable
인터페이스 초기 버젼은 다음과 같은 메서드를 포함한다.
1 | public interface Resizable extends Drawable { |
사용자 구현
우리 라이브러리를 즐겨 사용하는 사용자 중 한 명은 직접 Resizable
을 구현하는 Ellipse
클래스를 만들었다.
1 | public class Ellipse implements Resizable { |
이 사용자는 다양한 Resizable
모양(자신이 만든 Ellipse
를 포함해서)을 처리하는 게임을 만들었다.
1 | public class Game { |
1 | public class Utils { |
API 버전 2
몇 개월이 지나자 Resizalbe
을 구현하는 Square
와 Rectangle
구현을 개선해달라는 많은 요청을 받았다.
1 | public interface Resizable extends Drawable { |
사용자가 겪는 문제
Resizable
을 고치면 몇가지 문제가 발생한다.
Resizable
을 구현하는 모든 클래스는setRelativeSize
메서드를 구현해야 한다. 하지만 라이브러리 사용자가 직접 구현한Ellipse
는setRelativeSize
메서드를 구현해야 한다.- 공개된 API를 고치면 기존 버전과의 호환성 문제가 발생한다.
- 사용자가 라이브러리를 관리하면 결국 프로젝트에서 로딩해야 할 클래스 파일이 많아지면서 메모리 사용과 로딩 시간 문제가 발생한다.
위와 같은 문제를 디폴트 메서드로 해결할 수 있다.
디폴트 메서드란 무엇인가?
자바8에서는 호환성을 유지하면서 API를 바꿀 수 있도록 새로운 기능인 디폴트 메서드(default method)
를 제공한다. 우선 디폴트 메서드는 defalut
라는 키워드로 시작하며 다른 클래스에 선언된 메서드처럼 메서드 바디를 포함한다.
1 | public interface Sized { |
이제
Sized
인터페이스를 구현하는 모든 클래스는isEmpty
의 구현도 상속 받는다. 즉, 인터페이스에 디폴트 메서드를 추가하면 소스 호환성이 유지된다.
추상 클래스와 자바 8의 인터페이스
추상 클래스와 인터페이스는 다른점은, 둘 다 추상 메서드와 바디를 포함하는 메서드를 정의 할 수 있다.
- 클래스는 하나의 추상 클래스만 상속받을 수 있지만 인터페이스를 여러 개 구현할 수 있다.
- 추상 클래스는 인스턴스 변수(필드)로 공통 상태를 가질 수 있다. 하지만 인터페이스는 인스턴스 변수를 가질 수 없다.
디폴트 메서드 활용 패턴
디폴트 메서드를 이용하는 두가 방식, 선택형 메서드(optional method)
와 동작 다중 상속(multiple inheritance of behavior)
을 설명한다.
선택형 메서드
예를 Iterator
인터페이스를 보자. Iterator
는 hasNext
와 next
뿐 아니라 remove
메서드도 정의한다. 사용자들이 remove
기능은 잘 사용하지 않으므로 자바 8 이전에는 remove
기능을 무시했다. 결과적으로 Iterator
를 구현하는 많은 클래스에서는 remove
에 빈 구현을 제공했다. 자바8의 Iterator
인터페이스는 다음처럼 remove
메서드를 정의한다.
1 | interface Iterator<T> { |
기본 구현이 제공되므로 Iterator
인터페이스를 구현하는 클래스는 빈 remove
메서드를 구현 할 필요가 없어졌고, 불필요한 코드를 줄일 수 있다.
동작 다중 상속
자바에서 클래스는 한 개의 다른 클래스만 상속할 수 있지만 인터페이스는 여러 개 구현할 수 있다. 다음은 자바 API에 정의된 ArrayList
클래스다.
1 | public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, Serializable, Iterable<E>, Collection<E> { |
해석 규칙
- 클래스가 항상 이긴다. 클래스나 슈퍼 클래스에서 정의한 메서드가 디폴트 메서드보다 우선권을 갖는다.
- 위 규칙 이외의 상황에서는 서브인터페이스가 이긴다. 상속관계를 갖는 인터페이스에서 같은 시그너처를 갖는 메서드를 정의할 때는 서브인터페이스가 이긴다. 즉, B가 A를 상속받는다면 B가 A를 이긴다.
- 여전히 디폴트 메서드의 우선순위가 결정되지 않았다면 여러 인터페이스를 상속 받는 클래스가 명시적으로 디폴트 메서드를 오버라이드하고 호출해야 한다.