본문 바로가기

책리뷰/모던자바인액션

모던자바인액션 13강 디폴트 메서드

728x90
반응형

❓ 디폴트 메서드가 나온 배경

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

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

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

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

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

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

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

 

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

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

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

 

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

 

인터페이스에 메서드 추가

⚫ 정적메서드와 인터페이스

인터페이스의 인스턴스를 활용할 수 있는 정적메서드를 정의하는 유틸리티 클래스가 많이 있다.

예를 들면 Collections는 Collection객체를 활용할 수 있는 유틸리티 클래스다. 자바8에서는 인터페이스에 직접 정적 메서드를 선언할 수 있다.

 

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

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

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

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

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

 

⚫ 추상클래스 vs 자바8 인터페이스

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

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

 

 

디폴트 메서드 활용 패턴

1. 선택형메서드

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

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

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

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

 

2. 다중상속

 

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

자바8에서는 인터페이스가 구현을 포함할 수 있기 때문에 여러 인터페이스에서 구현코드를 상속받을 수 있다. 

필요한 메서드가 있는 인터페이스를 구현하고 디폴트 메서드는 구현할 필요가 없다.

 

3. 위임과 포함

상속으로 재사용을 어느 정도 해결할 수 있지만 한 개의 메서드를 이용하기 위해 100개의 메서드와 필드가 있는 클래스를 상속받는 것은 좋지 않다. 이럴 때 위임( Delegation) 즉, 멤버 변수를 이용해서 클래스에서 필요한 메서드를 직접 호출하는 메서드를 작성 하는 것이 좋다.(스프링에서 DI처럼)

final로 선언된 클래스를 볼 수 있는데 다른 클래스가 이 클래스를 상속받지 못하게 함으로써 원래 동작이 바뀌지 않길 원하기 때문이다. 

 

 

⚫ 해석 규칙

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

new C().hello(); 는 무엇이 호출될까?

알아야할 세 가지 규칙

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

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

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

 

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

 

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

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

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

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

 

 

728x90
반응형