Default Method - 1

Daniel6364·2022년 11월 2일
0

Default Method

목록 보기
1/1
  • 인터페이스와 관련 메서드는 한 몸처럼 구성된다.
  • 인터페이스를 구현하는 클래스는 인터페이스에서 정의하는 모든 메서드 구현을 제공하거나 아니면 슈퍼클래스의 구현을 상속받아야 한다.
  • 이 때문에 설계자 입장에서는 인터페이스에 새로운 메서드를 추가하거나 수정하고 싶을 때는 문제가 발생한다.
  • 인터페이스를 변경하면 이전에 해당 인터페이스를 구현했던 모든 클래스의 구현도 고쳐야 하기 때문이다.

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

  1. 인터페이스 내부에 정적 메서드(static method)를 사용하는 것.
  2. 인터페이스의 기본 구현을 제공할 수 있도록 디폴트 메서드(default method) 기능을 사용하는 것.

자바 8에서는 메서드 구현을 포함하는 인터페이스를 정의 할 수 있다.

-> 결과적으로 기존 인터페이스를 구현하는 클래스는 자동으로 인터페이스에 추가된 새로운 메서드의 디폴트 메서드를 상속받게 된다.
(ex. List 인터페이스의 sort 메서드, Collection 인터페이스의 stream 메서드)

List<Integer> numbers = Arrays.asList(3, 5, 1, 2, 6);
numbers.sort(Comparator.naturalOrder()); 
//-> sort는 List 인터페이스의 디폴트 메서드이다.
  1. 디폴트 메서드는 주로 라이브러리 설계자들이 사용.
  2. 디폴트 메서드를 이용하면 자바 API의 호환성을 유지하면서 라이브러리를 바꿀 수 있다.
  3. 디폴트 메서드를 이용하면 인터페이스의 기본 구현을 그대로 상속하므로 인터페이스에 자유롭게 새로운 메서드를 추가할 수 있게 된다.
  4. 디폴트 메서드는 다중 상속 동작이라는 유연성을 제공하면서 프로그램 구성에도 도움을 준다.

디폴트 메서드에 대해 살펴볼 내용

  1. API가 바뀌면서 어떤 문제가 생기는지 확인
  2. API가 바뀌면서 발생한 문제를 디폴트 메서드로 어떻게 해결할 수 있는지
  3. 디폴트 메서드를 만들어 다중 상속을 달성하는 방법
  4. 여러 디폴트 메서드를 상속받으면서 발생하는 모호성 문제를 자바 컴파일러가 어떻게 해결하는지

예시

아래와 같은 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을 구현하는 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 setAbsoluteSize(int width, int height);  
  void setRelativeSize(int widthFactor, int heightFactor);
}

발생되는 문제점
1. Resizable을 구현하는 모든 클래스는 setRelativeSize 메서드를 구현해야 한다.

2. 인터페이스에 새로운 메서드를 추가하면 바이너리 호환성은 유지된다. 바이너리 호환성이란 새로 추가된 메서드를 호출하지만 않으면 새로운 메서드 구현이 없이도 기존 클래스 파일 구현이 잘 동작한다는 의미다. 하지만, Resizable을 인수로 받는 클래스에서 추가된 메서드를 사용하도록 코드를 바꾸게 되면 정의되지 않은 메서드에 대해서 런타임 에러가 발생한다.

3. 재빌드시 컴파일 에러가 발생한다.

// 2번에 대한 예제

// 1. 사용자가 Resizable 인터페이스를 상속받아 구현했다.
// 2. Resizable 인터페이스에 추가된 'setRelativeSize' 메서드에 대해서 추가 구현하지 않았다.
public class Ellipse implements Resizable {

  @Override
  public int getWidth() {
    return 0;
  }

  @Override
  public int getHeight() {
    return 0;
  }

  @Override
  public void setWidth(int width) {
  }

  @Override
  public void setHeight(int height) {
  }

  @Override
  public void setAbsoluteSize(int width, int height) {
  }

  @Override
  public void draw() {
  }

}

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);
    });

    // Ellipse 클래스에서 아래 setRelativeSize를 구현하지 않았기 때문에 해당 구문에서 런타임 에러가 발생한다.
    l.forEach(r -> { r.setRelativeSize(2, 2); });
  }

}

Exception in thread "main" java.lang.AbstractMethodError
: lambdasinaction.chap9.Ellipse.setRelativeSize(II)V

발생되는 문제점
4. 공개된 API를 고치면 기존 버전과의 호환성 문제가 발생한다. 이런 이유 때문에 공식 자바 컬렉션 API같은 기존의 API는 수정이 어렵다.

5. 자신만의 API를 별도로 만들어 새로운 버전을 직접 관리하는 방법도 있지만, 이는 여러 가지로 불편하다. 이유는 첫째, 라이브러리를 관리하기가 복잡하고 둘째, 사용자는 같은 코드에 이전 버전과 새로운 버전 두가지 라이브러리를 모두 사용해야 하는 상황이 생긴다. 이는 프로젝트에 로딩해야 할 클래스 파일이 많아지면서 메모리 사용과 로딩 시간 문제가 발생한다.

디폴트 메서드로 이 모든 문제를 해결할 수 있다.

디폴트 메서드를 이용해서 APi를 바꾸면 새롭게 바뀐 인터페이스에서 자동으로 기본 구현을 제공하기 때문에 기존 코드를 고치치 않아도 된다.

출처 : 모던 자바 인 액션

profile
The Office Lover

0개의 댓글