#13장 Default Method

이 장의 내용

  • 디폴트메서드란무엇인가?

  • 진화하는 API가 호환성을 유지하는 방법

  • 디폴트메서드의 활용 패턴

  • 해결규칙

들어가기

  • 인터페이스와 관련 메서드는 한 몸처럼 구성

  • 인터페이스에 새로운 메서드를 추가하는 등 인터페이스를 바꾸고 싶을 때 문제가 발생

  • 자바 8에서는 이 문제를 해결하는 새로운 기능을 제공

  • 해결 방법

    • static method

    • default method

  • 즉, 메서드 구현을 포함하는 인터페이스를 정의할 수 있다.

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

// sort의 구현 코드
// 새로운 디폴트 메서드 덕분에 리스트에 직접 sort를 호출할 수 있게 되었다.
default void sort(Comparator<? super E> c){
    Collections.sort(this, c);
}

디폴트 메서드가 생기면서 결국 인터페이스가 아니라 추상 클래스 아닌가? 어떤점이 다른지 추후 살펴보자.

디폴트 메서드는 주로 라이브러리 설계자들이 사용한다.

자바 API 의 호환성을 유지하면서 라이브러리를 바꿀 수 있다. (추가된 메서드에 대한 구현의 강제로 인해 클래스를 바꿔야하는 상황이 오지 않으니.)

Collection안에 정적 메서드를 구현할 수 있음에도 Collections의 유틸리티 클래스를 만든 이유: 과거 버전과의 호환성을 유지 위해

  • 아래서 살펴볼것

    • API가 바뀌면서 발생한 문제를 디폴트 메서드로 어떻게 해결할 수 있는지

    • 디폴트 메서드를 만들어 다중 상속을 달성

    • 같은 시그니처를 갖는 여러 디폴트 메서드를 상속받으면서 발생하는 모호성 문제를 자바 컴파일러가 어떻게 해결하는지

진화하는 APIs

사례: Resizable 인터페이스

API version 1

구현자가 Resizable 모양을 처리하는 게임을 가정한다.

API version 2

Resizable을 구현하는 Square와 Rectangle 구현을 개선해달라는 많은 요청이 와서, 버전 2에 아래와 같이 setRelativeSize()을 만들었다.

문제점: 언젠가는 누군가가 Resizable을 인수로 받는 Utils.paint에서 setRelativeSize를 사용하도록 코드를 바꿀 수 있다. 이때 Ellipse 객체가 인수로 전달되면 Ellipse는 setRelativeSize 메서 드를 정의하지 않았으므로 런타임 에러가 발생할 것 이다. 또한, 사용자가 Ellipse를 포함하는 전체 애플리케이션을 재빌드할 때 컴파일 에러도 발생한다.

즉, 공개된 API를 고치면 기존 버전과의 호환성 문제가 발생한다.

해결방법중 하나로, 자신만의 API를 별도로 만든 다음에 예전 버전과 새로운 버전을 직접 관리하는 방법도 있지만, 불편한 점이 많다. (e.g. 사용자가 2개 라이브러리 모두 사용해야하는 상황)

바이너리 호환성이란?

새로 추가된 메서드를 호출하지만 않으면 새로운 메서드 구현이 없이도 기존 클래스 파일 구현이 잘 동작한다는 의미 (뭔가를 바꾼 이후에도 에러 없이 기존 바이너리가 실행될 수 있는 상황)

소스 호환성이란?

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

동작 호환성이란?

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

디폴트 메서드란 무엇인가?

자바에서 여러 인터페이스를 동시에 구현할 수 있으므로 결국 다중 상속을 지원하는 걸까?

구현 클래스가 디폴트 메서드와 같 은 메서드 시그•니처를 정의하거나 아니면 디폴트 메서드를 오버라이드한다면 어떻게 될까? (13.4에서 자세히)

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

  • 단일 상속 vs 다중 상속

    • 클래스는 하나의 추상 클래스만 상속받을 수 있지만 인터페이스를 여러 개 구현할 수 있다.

  • 변수 상태 공유 여부

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

퀴즈

ArrayList, TreeSet, LinkedList 및 다른 모든 컬렉션에서 사용할 수 있는 removeIf 메서드를 추가해달라 는 요청을 받았다. 메서드는 주어진 Predicate와 일치하는 모든 요소를 컬렉션에서 제거하는 기능을 수행한다. 새로운 removelf를 기존 컬렉션 API에 가장 적절하게 추가하는 방법은 무엇일까?

답: 공통적으로 상속한 Collection 인터페이스에 디폴트 메소드 추가한다.

Usage patterns for 디폴트 메서드

디폴트 메서드를 다른 방식으로도 활용할 수 있을까?

여기선 디폴트 메서드를 이용하는 두 가지 방식, optional method와 multiple inheritance of behavior에 대해 설명한다.

선택형 메서드

Iterator의 remove 기능은 사용자들이 잘 사용하지 않으므로 자바 8 이전에는 remove 기능을 무시하여, 사용자들은 remove를 빈 상태로 놔뒀다.

동작 다중 상속

클래스는 한개만 상속 가능하기에, 인터페이스를 여러개 상속 받아 여러 동작을 다양하게 상속 받을 수 있다.

기능이 중복되지 않는 (ORTHOGONAL FUNCTIONALITIES) 최소의 인터페이스

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

아래를 모두 상속하면, 움직일 수 있고, 회전할 수 있으며, 크기를 조절할 수 있는 괴물 클래스를 구현할 수 있다.

조합으로 만든 괴물 클래스 (디폴트 메소드의 파워)

동일 디폴트 메서드 시그니처를 위한 해결 규칙

만약 어떤 클래스가 같은 디폴트 메서드 시그니처를 포함하는 두 인터페이스를 구현하는 상황이라면 어떻게 될까? 아래 사례를 보자.

C++ 다이아몬드 문제란?

같은 시그니처를 갖는 두 메서드를 상속받는 클래스 상황

3가지 해결 규칙

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

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

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

규칙 2번에 따라 위 B의 hello()가 호출된다.

그럼 아래와 같은 상황은 어떻게 될까?

답: B의 hello()

해설: D는 hello를 오버라이드하지 않았고 단순히 인터페이스 A를 구현했다. 따라서 D는 인터페이스 A의 디폴트 메서드 구현을 상속 받는다. 2번 규칙에서는 클래스나 슈퍼클래스에 메서드 정의가 없을 때는 디폴트 메서드를 정의하 는 서브인터페이스가 선택된다. 따라서 컴파일러는 인터페이스 A의 hello나 인터페이스 B의 hello 둘 중 하나를 선택해야 한다. 여기서 B가 A를 상속받는 관계이므로 이번에도 ‘Hello from B’가 줄력된다.

퀴즈

답: D의 hello()

해설: 규칙 1에 의해

충돌 그리고 명시적인 disambiguation

아래는 1번, 2번 규칙이 적용되지 않는다. 결국, 에러가 발생한다.

해결을 위해, 명시적으로 선택한다.

퀴즈

출력 결과는?

답: 컴파일 에러

다이아몬드 문제

아래는 어떤 결과가 나올까?

정답: A의 hello()

해설: 실제로 선택할 수 있는 메서드 선언은 하나뿐이기에

Last updated