Backend/JAVA

[JAVA] default 메소드에 대하여(모던 자바 인 액션)

기만주 2024. 4. 10. 19:32

자바 인터페이스

  • 자바 인터페이스를 통해 구현하는 클래스는 인터페이스에서 정의하는 모든 메서드 구현을 제공하거나 슈퍼클래스의 구현을 상속 받아야 한다.
  • 평소에는 이 규칙을 지키는 데 아무 문제가 없지만, 라이브러리 설계자 입장에서 인터페이스에 새로운 메서드를 추가하는 등 인터페이스를 변경하게되면 문제가 발생한다.(바이너리 호환성은 유지)

바이너리 호환성?

새로 추가된 메서드를 호출하지만 않으면 새로운 메서드 구현이 없어도 기존 클래스 파일 구현이 잘 동작한다는 의미. 바이너리 실행에는 인증, 준비, 해석 등의 과정이 포함 됨.그 외 호환성

  1. 소스 호환성
    코드를 고쳐도 기존 프로그램을 성공적으로 재컴파일할 수 있음을 의미
    인터페이스에 메서드 추가하는건 소스 호환성을 만족시키지 않는다. 코드를 수정해야하기 때문.
  2. 동작 호환성
    코드를 바꾼 다음 같은 입력값이 주어지면 프로그램이 같은 동작을 실행한다는 의미
    인터페이스에 메서드를 추가하더라도 프로그램에서 추가된 메서드를 호출할 일은 없으므로 동작 호환성은 유지
  • 자바 8이상부턴 이 부분을 해결하는 새로운 기능을 제시한다

해결법 - 디폴트 메소드(default method)

  • 자바 8 이상에서는 메서드 구현을 포함하는 인터페이스를 정의할 수 있다.
  • 인터페이스를 구현하는 클래스는 자동으로 디폴트 메소드를 상속받는다.
  • 기존의 코드를 바꾸지 않아도 인터페이스를 바꿀 수 있다.

public interface Sized{
    int size();
    default boolean isEmpty(){
        return size() == 0;
    }

}

그럼 이건 다중 상속인가??

  • 인터페이스가 구현을 갖고 있고, 여러 인터페이스를 구현할 수 있으니 다중상속인가??

 

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

  • 둘 다 추상 메서드와 바디를 포함하는 메서드를 정의할 수 있다.
  • 클래스는 하나의 추상 클래스만 상속받을 수 있지만 인터페이스는 여러 개를 구현할 수 있다.
  • 추상 클래스는 인스턴스 변수(필드)로 공통 상태를 가질 수 있다. 하지만 인터페이스는 인스턴스 변수를 가질 수 없다.

 

디폴트 메서드 활용 패턴

  • 디폴트 메서드를 다른 방식으로 활용해보자
  • 선택형 메서드(optional method)와 동작 다중 상속(multiple inheritance of behavior)

 

1. 선택형 메서드

  • 인터페이스를 구현하는 클래스 중 메서드의 내용이 비어있는 상황이 있다.
  • Iterator 인터페이스의 경우 hasNext와 next, remove 메서드를 정의한다.
    • 이전에는 remove를 잘 사용하지 않아 빈 구현을 제공했으나, 현재는 default 메서드로 선언되어 있다.
    • API 인터페이스에서 기본 기능을 제공한다.
interface Iterator<T>{
    boolean hasNext();
    T nexT();
    default void remove(){
        throw new UnsupportedOperationException();
    }

}

2. 동작 다중 상속

  • 기존에 불가능했던 동작 다중 상속 기능을 구현할 수 있다.
  • 자바에서 클래스는 한 개의 다른 클래스만 상속할 수 있지만, 인터페이스는 여러 개 구현할 수 있다.

  • ArrayList 예시
    • 한 개의 클래스를 상속받고, 여섯 개의 인터페이스를 구현한다.
    • 결과적으로 AbstractList, List, RandomAccess, Cloneable, Serializable, Iterable, Collection의 서브형식(subtype)이 된다.
    • 따라서, 디폴트 메서드를 사용하지 않아도 다중 상속을 활용할 수 있다.
public class ArrayList<E> extends AbstractList<E> 
implements List<E>, RandomAccess, Cloneable, Serializable {
}
  • 기능이 중복되지 않는 최소의 인터페이스
    • 만약, 다양한 특성을 갖는 여러 모양을 정의해야 하는 경우를 가정해보자
    • 어떤 모양은 회전할 수 없고 크기는 조절할 수 없다.
    • 또다른 모양은 회전할 수 있고, 움직일 수도 있지만 크기를 조절할 수 없다.
    • 이 경우 Rotatable, Movable, Resizable을 작성할 수 있다.
    • Rotatable의 경우 setRotationAngle, getRoationAngle은 정의해야 하지만, ratateBy는 기본 기능이 제공된다.

 

public interface Rotatable { 
    void setRotationAngle(int anglelnDegrees); 
    int getRotationAngle(); 
    default void rotateBy(int anglelnDegrees) { 
	setRotationAngle((getRotationAngle () + anglelnDegrees) % 360); 
}

 

  • 인터페이스 조합
    • 이제 위에서 만든 세가지 내용을 조합하여 다양한 클래스를 만들 수 있다.
    • Monster 는 회전가능하며, 움직일 수 있고, 사이즈 조절이 가능하다.
public class Monster implements Rotatable, Moveable, Resizable { }

해석 규칙

  • 자바는 여러 인터페이스를 동시에 구현할 수 있다.
  • 그렇다면, 디폴트 메서드를 구현한 여러 인터페이스를 한번에 구현하게 되면, 같은 시그니처를 갖는 경우 어떻게 해야할 까?
  • 또, 클래스와 인터페이스 사이에 생길 수 있는 문제다.
  • 규칙을 정해야 한다.
  • 세가지 해결 규칙
    1. 클래스가 항상 이긴다. 클래스는 슈퍼클래스에서 정의한 메서드가 디폴트 메서드보다 우선권을 갖는다.
    2. 1번 이외의 상황에서는 서브인터페이스가 이긴다.
    3. 여전히 디폴트 우선순위가 결정되지 않았다면 여러 인터페이스를 상속받는 클래스가 명시적으로 디폴트 메서드를 오버라이드하고 호출해야 한다.
      • 자바 컬파일러는 위같은 상황에 에러가 발생하며, 충돌 해결해주어야 한다.
      • 직접 어떤 인터페이스의 내용을 사용할지 명시한다.