디폴트 메서드
#Java/adv3
디폴트 메서드가 등장한 이유
- 자바 8에서 디폴트 메서드(default method) 가 등장하기 전에는 인터페이스에 메서드를 새로 추가하는 순간, 이미 배포된 기존 구현 클래스들이 해당 메서드를 구현하지 않았기 때문에 전부 컴파일 에러를 일으키게 되는 문제가 있었다.
- 디폴트 메서드는 이러한 문제를 해결하기 위해 등장했다.
- 자바 8부터는 인터페이스에서 메서드 본문을 가질 수 있도록 허용해 주어, 기존 코드를 깨뜨리지 않고 새 기능을 추가할 수 있게 되었다.
디폴트 메서드로 문제 해결
자바 8부터 이러한 하위 호환성 문제를 해결하기 위해 디폴트 메서드가 추가되었다. 인터페이스에 메서드를 새로 추가하면서, 기본 구현을 제공할 수 있는 기능이다.
예를 들어, Notifier
인터페이스에 scheduleNotification()
메서드를 default 키워드로 작성하고 기본 구현을 넣어두면, 구현 클래스들은 이 메서드를 굳이 재정의하지 않아도 된다.
package defaultmethod.ex2;
import java.time.LocalDateTime;
public interface Notifier {
// 알림을 보내는 기본 기능
void notify (String message);
// 신규 기능 추가
// void scheduleNotification(String message, LocalDateTime scheduleTime); // 주석
// default 키워드를 사용해 기본 구현 제공
default void scheduleNotification(String message, LocalDateTime scheduleTime) {
System.out.println("[기본 스케줄링] message: " + message + ", time: " + scheduleTime);
}
}
- 구현 클래스에서는 필요한 경우에만 재정의하면 됨
- 재정의하지 않으면 인터페이스에 작성한 기본 구현이 그대로 사용됨
디폴트 메서드 소개
자바는 처음부터 인터페이스와 구현을 명확하게 분리한 언어였고, 인터페이스는 구현 없이 메서드의 시그니처만을 정의하는 용도로 사용되었다.
- 인터페이스 목적: 코드의 계약(Contract)을 정의하고, 클래스가 어떤 메서드를 반드시 구현하도록 강제하여 명세와 구현을 분리하는 것이 주된 목적이었다.
- 엄격한 규칙: 인터페이스에 선언되는 메서드는 기본적으로 모두 추상 메서드(abstract method)였으며, 인터페이스 내에서 구현 내용을 포함할 수 없었다. 오직
static final
필드와abstract
메서드 선언만 가능했다. - 결과: 이렇게 인터페이스가 엄격하게 구분됨으로써, 클래스는 여러 인터페이스를 구현(implements)할 수 있게 되고, 각각의 메서드는 클래스 내부에서 구체적으로 어떻게 동작할지를 자유롭게 정의할 수 있었다. 이를 통해 객체지향적인 설계와 다형성을 극대화할 수 있었다.
만약 자바가 버전 업을 하면서 해당 인터페이스에 새로운 기능을 추가한다면 어떻게 될까? 전 세계에서 자바 Application에서 컴파일 오류가 발생하게 되버린다. 이런 문제를 방지하기 위해 자바는 하위호환성을 그 무엇보다 큰 우선순위에 둔다.
결국 인터페이스의 이런 엄격한 규칙 때문에, 그 동안 자바 인터페이스에 새로운 기능을 추가하지 못하는 일이 발생하게 되었다.
이런 문제를 해결하기 위해 자바 8에서 디폴트 메서드가 도입되었다.
- 하위 호환성(Backward Compatibility) 보장: 인터페이스에 새로운 메서드를 추가하더라도, 기존 코드가 깨지지 않도록 하기 위한 목적으로 디폴트 메서드가 도입되었다. 인터페이스에 디폴트 구현(기본 구현)을 제공하면, 기존에 해당 인터페이스를 구현하던 클래스들은 추가로 재정의하지 않아도 정상 동작하게 된다.
- 라이브러리 확장성: 자바가 제공하는 표준 라이브러리에 정의된 인터페이스(예:
Collection
,List
)에 새 메서드를 추가하면서, 사용자들이나 서드파티 라이브러리 구현체가 일일이 수정하지 않아도 되도록 만들었다. 이를 통해 자바 표준 라이브러리 자체도 적극적으로 개선할 수 있게 되었다.- 예:
List
인터페이스에sort(...)
메서드가 추가되었지만, 기존의 모든List
구현체를 수정하지 않아도 된다.
- 예:
- 람다와 스트림 API 연계: 자바 8에서 함께 도입된 람다(Lambda)와 스트림(Stream) API를 보다 편리하게 활용하기 위해 인터페이스에서 구현 로직을 제공할 필요가 있었다.
Collection
인터페이스에stream()
디폴트 메서드 추가Iterable
인터페이스에forEach
디폴트 메서드 추가
- 설계 유연성 향상: 디폴트 메서드를 통해 인터페이스에서도 일부 공통 동작 방식을 정의할 수 있게 되었다. 이는 추상 클래스와의 경계를 어느 정도 유연하게 만들지만, 동시에 지나치게 복잡한 기능을 인터페이스에 넣는 것은 오히려 설계를 혼란스럽게 만들 수 있으므로 주의해야 한다.
디폴트 메서드의 올바른 사용법
- 하위 호환성을 위해 최소한으로 사용: 기존 구현 클래스가 많은게 아니라면 추상 메서드로 추가하자. 디폴트 메서드 남용은 코드 복잡도를 높일 수 있다.
- 인터페이스는 여전히 추상화의 역할: 인터페이스에 로직을 넣을 수 있지만, 로직은 가능한 별도로 두고 계약의 역할에 충실!
- 다중 상속(충돌) 문제 어떤 인터페이스의 디폴트 메서드 쓸 것인지 명시
- 직접 구현
A.super.hello();
B.super.hello();
- 디폴트 메서드에 상태(state)를 두지 않기
'Java > Modern Java(8~)' 카테고리의 다른 글
[Java] 68. 병렬 스트림 (0) | 2025.07.04 |
---|---|
[Java] 66. Optional (1) | 2025.07.04 |
[Java] 65. 스트림 API3 - 컬렉터 (0) | 2025.07.04 |
[Java] 64. 스트림 API2 - 기능 (0) | 2025.07.04 |
[Java] 63. 스트림 API1 - 기본 (0) | 2025.07.04 |