9. 디폴트 메서드


전통적인 자바에서 인터페이스와 관련 메서드는 한 몸처럼 구성.

인터페이스를 구현하는 클래스는 인터페이스에서 정의하는 모든 메서드 구현을 제공하거나 아니면 슈퍼클래스의 구현을 상속 받아야 함.

라이브러리 설계자 입장에서 인터페이스에 새로운 메서드를 추가하는 등 인터페이스를 변경하고 싶을 때는 문제가 발생함.

> 인터페이스를 바꾸면 이전에 해당 인터페이스를 구현했던 모든 클래스의 구현도 고쳐야 하기 때문.

자바8에서는 기본 구현을 포함하는 인터페이스를 정의하는 두 가지 방법을 제공.

1) 인터페이스 내부에 정적 메서드 사용

2) 인터페이스의 기본 구현을 제공할 수 있도록 디폴트 메서드 기능 사용.

> 즉, 자바8은 메서드 구현을 포함하는 인터페이스 정의 가능.

기존 인터페이스를 구현하는 클래스는 자동으로 인터페이스에 추가된 새로운 메서드의 디폴트 메서드를 상속.

default 키워는 해당 메서드가 디폴트 메서드임을 가리킴.

디폴트 메서드를 사용 하는 이유는?

- 디폴트 메서드를 이용하면 자바 API의 호환성을 유지하면서 라이브러리 수정 가능.

디폴트 메서드를 이용하면 인터페이스의 기본 구현을 그대로 상속하므로 인터페이스에 자유롭게 새로운 메서드 추가 가능.

다중 상속 동작이라는 유연성을 제공하면서 프로그램 구성에 도움을 줌.


정적 메서드와 인터페이스

보통 자바에서는 인터페이스 그리고 인터페이스의 인스턴스를 활용할 수 있는 다양한 정적 메서드를 정의하는 유틸리티 클래스를 사용.

> ex) Collections는 Collection 객체를 활용 할 수 있는 유틸리티 클래스.

자바8에서는 인터페이스에 직접 정적 메서드를 선언할 수 있으므로 유틸리티 클래스를 없애고 직접 인터페이스 내부에 정적 메서드 구현 가능.

> 그럼에도 불구하고 과거 버전과의 호환성을 유지할 수 있도록 자바 API에는 유틸리티 클래스가 남아있음.


1. 변화하는 API

- 공개된 API를 수정하면 기존 버전과의 호환성 문제 발생.

> 이러한 이유로 공식 자바 컬렉션 API 같은 기존의 API는 수정하기 어려움.

> API를 바꿀 수 있는 몇 사지 대안이 존재하지만 완벽한 해결책은 될 수 없음.

> ex) 자신만의 API를 별도로 만든 다음에 예전 버전과 새로운 버전을 직접 관리하는 방법등...

- 디폴트 메서드로 이 모든 문제들 해결 가능.

> 디폴트 메서드를 이용해 API 수정 시 새롭게 바뀐 인터페이스에서 자동으로 기본 구현을 제공하므로 기존 코드를 수정하지 않아도 됨.


바이너리 호환성, 소스 호환성, 동작 호환성

자바 프로그램을 바꾸는 것과 관련된 호환성 문제는 크게 바이너리 호환성, 소스 호환성, 동작 호환성 3가지로 분류 가능.

인터페이스에 메서드를 추가했을 때는 바이너리 호환성을 유지하지만 인터페이스를 구현하는 클래스를 재컴파일하면 에러가 발생.

즉, 다양한 호환성이 있다는 사실을 이해해야 함.


바이너리 호환성: 뭔가를 바꾼 이후에도 에러가 없이 기존 바이너리가 실행될 수 있는 상황.

> 바이너리 실행에는 인증, 준비, 해석 등의 과정이 포함.

> 예로 인터페이스에 메서드를 추가했을 때 추가된 메서드를 호출하지 않는 한 문제가 발생하지 않는데 이를 바이너리 호환성이라 함.


소스 호환성: 코드를 고쳐도 기존 프로그램을 성공적으로 재컴파일할 수 있음을 의미.

> 예로 인터페이스에 메서드를 추가하면 소스 호환성이 아님.

> 추가한 메서드를 구현하도록 클래스를 고쳐야 하기 때문.


동작 호환성: 코드를 바꾼 다음에도 같은 입력값이 주어지면 프로그램이 같은 동작을 실행한다는 의미.

> 예로 인터페이스에 메서드를 추가하더라도 프로그램에서 추가된 메서드를 호출할 일이 없으므로 동작 호환성 유지.

> 혹 우현히 구현 클래스가 이를 오버라이드 한 경우도 존재.


2. 디폴드 메서드란 무엇인가?

- 인터페이스는 자신을 구현하는 클래스에서 메서드를 구현하지 않을 수 있는 새로운 메서드 시그니처를 제공.

- 인터페이스를 구현하는 클래스에서 구현하지 않은 메서드는 인터페이스 자체에서 기본 제공.

- default라는 키워드로 시작하며 다른 클래스에 선언된 메서드처럼 메서드 바디를 포함.

- 인터페이스에 디폴트 메서드를 추가하면 소스 호환성이 유지.

- 함수형 인터페이스는 오직 하나의 추상 메서드를 포함. 디폴트 메서드는 추상 메서드에 해당하지 않음.


추상 클래스와 자바 8의 인터페이스

추상 클래스와 인터페이스의 차이점

1) 클래스는 하나의 추상 클래스만 상속 받을 수 있지만 인터페이스를 여러 개 구현 가능.

2) 추상 클래스는 인스턴스 변수(필드)로 공통 상태를 가질 수 있음. 하지만 인터페이스는 인스턴스 변수를 가질 수 없음.


3. 디폴트 메서드 활용 패턴

- 우리가 만드는 인터페이스에도 디폴트 메서드 추가 가능.

> 디폴트 메서드 이용하는 두 가지 방식

1) 선택형 메서드

2) 동작 다중 상속


3.1 선택형 메서드

- 디폴트 메서드를 이용하면 기본 구현을 제공할 수 있으므로 인터페이스를 구현하는 클래스에서 빈 구현을 제공할 필요가 없음.


3.2 동작 다중 상속

- 자바에서 클래스는 한 개의 다른 클래스만 상속할 수 있지만 인터페이스는 여러 개 구현할 수 있음.


다중 상속 형식

- ArrayList는 한 개의 클래스를 상속 받고, 여섯 개의 인터페이스를 구현

> 결과적으로 AbstractList, List, RandomAccess, Cloneable, Serializable, Iterable, Collection의 서브 형식이 됨.

> 따라서 디폴트 메서드를 사용하지 않아도 다중 상속을 활용.

- 자바8에서는 인터페이스가 구현을 포함할 수 있으므로 클래스는 여러 인터페이스에서 동작(구현 코드)를 상속 받을 수 있음.

- 중복되지 않는 최소한의 인터페이스를 유지한다면 우리 코드에서 동작을 쉽게 재사용하고 조합 가능.


인터페이스 조합

- 인터페이스에 디폴트 구현을 포함시키면 또 다른 장점 발생.

> 디폴트 메서드 덕분에 인터페이스를 직접 고칠 수 있고 구현하는 모든 클래스도 자동으로 변경한 코드를 상속 받음.

* 구현 클래스에서 메서드를 정의하지 않은 상황에 한해서.


옳지 못한 상속

상속으로 코드 재사용 문제를 모드 해결할 수 있는 것은 아님.

> 한 개의 메서드를 재사용하려고 100개의 메서드와 필드가 정의되어 있는 클래스를 상속 받는 것은 좋지 않은 생각.

> 이 경우 델리게이션(delegation), 즉 멤버 변수를 이용해서 클래스에서 필요한 메서드를 직접 호출하는 메서드를 작성하는 것이 좋음.

> 종종 final로 선언된 클래스 확인 가능.

* 다른 클래스가 이 클래스를 상속받지 못하게 함으로써 원래 동작이 바뀌지 않길 원하기 때문.

우리의 디폴트 메서드에도 이 규칙 적용 가능.

필요한 기능만 포함하도록 인터페이스를 최소한으로 유지한다면 필요한 기능만 선택할 수 있으므로 쉽게 기능 조립 가능.


4. 해석 규칙

- 자바8에는 디폴트 메서드가 추가되었으므로 같은 시그니처를 갖는 디폴트 메서드를 상속받는 상황 발생 가능.


4.1 알아야 할 세 가지 해결 규칙

1) 클래스가 항상 이김. 클래스나 슈퍼클래스에서 정의한 메서드가 디플트 메서드보다 우선권을 갖음.

2) 1번 규칙 이외의 상황에서 서브인터페이스가 이김. 상속관계를 갖는 인터페이스에서 같은 시그니처를 갖는 메서드를 정의할 때는 서브 인터페이스가 이김.

> 즉, B가 A를 상속 받는다면 B가 A를 이김.

3) 여전히 디폴트 메서드의 우선순위가 결정되지 않았다면 여러 인터페이스를 상속받는 클래스가 명시적으로 디폴트 메서드를 오버라이드하고 호출해야 함.


4.2 디폴트 메서드를 제공하는 서브인터페이스가 이긴다.

4.3 충돌 그리고 명시적인 문제 해결

충돌 해결

- 클래스와 메서드 관계로 디폴트 메서드를 선택할 수 업슨 상황에서 선택할 수 있는 방법이 없음.

- 개발자가 직접 클래스에서 사용하려는 메서드를 명시적으로 선택해야 함.

> 자바8에서는 X.super.m(...) 형태의 새로운 문법을 제공.

* 여기서 X는 호출하려는 메서드 m의 슈퍼인터페이스.


5. 요약

- 자바8의 인터페이스는 구현 코드를 포함하는 디폴트 메서드, 정적 메서드 정의 가능.

- 디폴트 메서드의 정의는 default 키워드로 시작하며 일반 클래스 메서드처럼 바디를 갖음.

- 공개된 인터페이스에 추상 메서드를 추가하면 소스 호환성이 깨짐.

- 디폴트 메서드 덕분에 라이브러리 설계자가 API를 바꿔도 기존 버전과 호환성 유지 가능.

- 선택형 메서드와 동작 다중 상속에도 디폴트 메서드 사용 가능.

- 클래스가 같은 시그니처를 갖는 여러 디폴트 메서드를 상속하면서 생기는 충동 문제를 해결 하는 규칙 존재.

- 클래스나 슈퍼클래스에 정의된 메서드가 다른 디폴트 메서드 정의보다 우선.

> 이 외의 상황에서는 서브인터페이스에서 제공하는 디폴트 메서드가 선택됨.

- 두 메서드의 시그니처가 같고, 상속관계로도 충돌 문제를 해결 할 수 없을 때 디폴트 메서드를 사용하는 클래스에서 메서드를 오버라이드해서 어떤 디폴트 메서드를 호출할지 명시적으로 지정해야 함.

'Java8 > Java 8 in Action' 카테고리의 다른 글

새로운 날짜와 시간  (0) 2017.08.07
null 대신 Optional  (0) 2017.07.12
리팩토링, 테스팅, 디버깅  (0) 2017.07.03
병렬 데이터 처리와 성능  (0) 2017.06.29
스트림으로 데이터 수집  (0) 2017.06.29

+ Recent posts