본문 바로가기
그룹 스터디 공부(IT 서적)/모던 자바 인 액션

chapter 13 디폴트

by hanyugyeong 2023. 8. 25.
반응형
SMALL

자바 8이전에 자바에서는 인터페이스를 구현하는 클래스는 인터페이스에서 정의하는 모든 메서드를 구현하여 제공하거나 슈퍼클래스의 구현을 상속받아야 한다.

그런데 인터페이스에 새로운 메서드를 추가하는 경우가 있으면 해당 인터페이스를 구현했던 모든 클래스의 구현을 고쳐야 한다. 

자바8  API에서 List 인터페이스에 sort같은 메서드를 추가했기 때문에 문제가 발생할 수 있다.

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

첫 번째, 인터페이스 내부에 정적 메서드를 사용하는 것

두 번째, 인터페이스의 기본 구현을 제공할 수 있도록 디폴트 메서드 기능을 사용하는 것 

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

default void sort(Comparator<? super E> c) {
	Collections.sort(this, c);
}

List인터페이스의  sort메서드 구현코드다.

default 키워드는 디폴트 메서드임을 가리킨다. 

naturalOrder는 자연순서(표준 알파벳 순서)로 요소를 정렬할 수 있도록 Comparator객체를 반환하는 Comparator인터페이스에 추가된 새로운 정적 메서드다.

13.1 변화하는 API

 라이브러리 사용자가 만든 클래스를 우리가 어떻게 할 수는 없다. 바로 자바 라이브러리 설계자가 라이브러리를 바꾸고 싶을 때 같은 문제가 발생한다. 이미 릴리스된 인터페이스를 고치면 어떤 문제가 발생하는지 더 자세히 살펴보자.

 

13.1.1 API 버전 1

Resizable 인터페이스 초기 버전은 다음과 같은 메서드를 포함한다. 

// Resizable 인터페이스의 초기 버전
public interface Resizable extends Drawable {
	int getWidth();
	int getHeight();
	void setWidth(int width);
	void setHeight(int height);
	void setAbsoluteSize(int width, int height);
}

사용자 구현 

우리 라이브러리를 즐겨 사용하는 사용자 중 한 명은 직접 Resizable을 구현하는 Elipse 클래스를 만들었다.

// 사용자의 구현
public class Ellipse implementes Resizable {
	...
}

이 사용자는 다양한 Resizable 모양을 처리하는 게임을 만들었다.

public class Game {

  public static void main(String... args) {
    List<Resizable> resizableShapes = Arrays.asList(
        new Square(), new Triangle(), new Ellipse());
    Utils.paint(resizableShapes);
  }

}
public class Utils {

  public static void paint(List<Resizable> l) {
    l.forEach(r -> {
      r.setAbsoluteSize(42, 42); //각 모양에 setAbsoluteSize 호출
      r.draw();
    });

    //TODO: 주석 해제, 자세한 사항은 README 참고
    //l.forEach(r -> { r.setRelativeSize(2, 2); });
  }

}

13.1.2 API 버전 2

몇 개월이 지나자 Resizable을 구현하는 Square와 Rectangle 구현을 개선해달라는 많은 요청을 받았다.

public interface Resizable extends Drawable {

  int getWidth();
  int getHeight();
  void setWidth(int width);
  void setHeight(int height);
  void setAbsoluteSize(int width, int height);
  void setRelativeSize(int widthFactor, int heightFactor);

}

자바 프로그램을 바꿀 때 발생하는 호환성 문제

바이너리 호환성: 뭔가를 바꾼 후에도 에러없이 기존 바이너리가 실행될 수 있는 상황, 인터페이스에 메서드를 추가했을 때 추가된 메서드를 호출하지 않는 한 문제가 일어나지 않음

소스 호환성: 코드를 고쳐도 기존 프로그램을 성공적으로 재컴파일할 수 있음. 인터페이스에 메서드를 추가하면 소스 호환성이 아님.(컴파일시 에러)

동작 호환성: 코드를 바꾼 다음에도 같은 입력값이 주어지면 프로그램이 같은 동작을 실행한다는 의미. 인터페이스에 메서드를 추가하더라도 프로그램에서 추가된 메서드를 호출할 일이 없으므로 동작 호환성은 유지됨

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

자바 8에서는 호환성을 유지하면서 API를 바꿀 수 있도록 새로운 기능인 디폴트 메서드를 제공한다. 

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

그럼 디폴트 메서드는 누가 구현할까? 인터페이스를 구현하는 클래스에서 구현하지 않은 메서드는 인터페이스 자체에서 기본으로 제공한다. 

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

13.3 디폴트 메서드 활용 패턴 

디폴트 메서드를 이용하는 두 가지 방식, 즉 선택형 메서드와 동작 다중 상속을 설명한다.

13.3.1 선택형 메서드

default 메서드를 이용하면 잘 사용하지 않는 기능을 기본 구현으로 제공함으로써 인터페이스를 구현하는 클래스에서 

빈 구현을 제공할 필요가 없다.

예를 들어 자바8의 Iterator인터페이스의 remove메서드는 잘 사용하지 않는 기능으로 기본 구현이 제공된다.

불필요한 코드를 줄일 수 있다.

13.3.2 동작 다중 상속 

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

 

다중 상속 형식 

ArrayList는 한 개의 클래스를 상속받고, 여섯 개의 인터페이스를 구현한다.  디폴트 메서드를 사용하지 않아도 다중 상속을 활용할 수 있다. 

다중 상속이 어떤 장점을 제공하는지 예제로 살펴보자.

13.4 해석 규칙

자바의 클래스는 하나의 부모 클래스만 상속받을 수 있지만 여러 인터페이스를 동시에 구현할 수 있다. 

public interface A {
	default void hell() {
		System.out.println("Hello from A");
    }
}

public interface B extends A {
	default void hello() {
    	System.out.println("Hello from B");
    }
}

public class C implements B, A {
	public static void main(String ... args) {
    	new C().hell(); // 무엇이 출력될까?
    }
}

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

다른 클래스나 인터페이스로부터 같은 시그니처를 갖는 메서드를 상속받을 때는 세 가지 규칙을 따라야 한다.

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

- 위의 규칙 이외에서는 서브 인터페이스가 이긴다. 상속관계를 갖는 인터페이스에서 같은 시그니처를 갖는 메서드를 정의할 때는 서브인터페이스가 이긴다. B가 A를 상속받았으므로 B가 이긴다.

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

 

이 세 가지 규칙만 알면 모든 디폴트 메서드 해석 무제가 해결된다. 이제 예제로 더 자세히 살펴보자.

규칙에 따르면 위 예제는 Hello from B가 호출된다.

 

인터페이스에 상속관계가 없을 경우 구별할 기준이 없다.

따라서 파일러는 에러를 발생시킨다.

public class C implements B, A {
	void hello() {
    	B.super.hello();
    }
}

명시적으로 인터페이스 B의 메서드를 선택할 수 있다.

 

 

 

반응형
LIST