디폴트 메서드가 등장한 이유
자바 8에서 디폴트 메서드가 등장하기 전에는 인터페이스에 메서드를 새로 추가하는 순간, 이미 배포된 기존 구현 클래스들이 해당 메서드를 구현하지 않았기 때문에 컴파일 에러를 일으키게 되는 문제가 있었다.
-> 디폴트 메서드는 이러한 문제를 해결하기 위해 등장했다.
예시)
package defaultmethod.ex2;
import java.time.LocalDateTime;
public interface Notifier {
void notify(String msg);
// 신규 기능 추가!
default void scheduleNotification(String msg, LocalDateTime scheduleTime) {
System.out.println("기본 스케줄링 msg" + msg + ", time" + scheduleTime);
}
}
-> 디폴트 키워드로 작성 후 기본 구현을 넣어두면 구현 클래스들은 이 메서드를 굳이 재정의하지 않아도 됨
디폴트 메서드 소개
자바가 처음 등장했을 때부터 인터페이스는 구현 없이 메서드의 시그니처만을 정의하는 용도로 사용됨
> 인터페이스 목적 : 코드의 계약을 정의하고, 클래스가 어떤 메서드를 구현하도록 강제해 명세와 구현을 분리하는 것
> 엄격한 규칙 : 인터페이스에 선언되는 메서드는 기본적으로 모두 추상 메서드로 오직 static final 필드와 abstract 메서드 선언만 가능했음
-> 자바 8 이전까지는 인터페이스에 새로운 메서드 추가 시, 해당 인터페이스를 구현한 모든 클래스에서 그 메서드를 구현해야 했음
만약, Collection, List와 같은 인터페이스는 이미 많은 개발자들이 구현해 사용하고 있는데 자바가 버전 업을 하며 새 기능을 추가하게 된다면?
수많은 컴파일 오류들이 발생할 것..
이를 위해 자바 8에서 디폴트 메서드가 도입됨!
1. 하위 호환성 보장
: 인터페이스에 새 메서드를 추가하더라도 기존 코드가 깨지지 않도록 하기 위해
2. 라이브러리 확장성
: 자바가 제공하는 표준 라이브러리에 정의된 인터페이스에 새 메서드를 추가하며 사용자들이나 서드파티 라이브러리 구현체를 일일이 수정하지 않아도 되도록
3. 람다와 스트림 API 연계
: 자바 8에서 함께 도입된 람다와 스트림 API를 보다 편리하게 활용하기 위해
: Collection 인터페이스에 stream() 디폴트 메서드 추가
: Iterable 인터페이스에 forEach 디폴트 메서드 추가
4. 설계 유연성 향상
: 인터페이스에서도 일부 공통 동작 방식 정의 가능
: 추상 클래스와의 경계를 어느 정도 유연하게 만들고 동시에 지나치게 복잡한 기능을 인터페이스에 넣는 것은 오히려 설계를 혼란스럽게 만들 수 있어 주의해야 함
디폴트 메서드의 올바른 사용법
1. 하위 호환성을 위해 최소한으로 사용
: 디폴트 메서드는 주로 이미 배포된 인터페이스에 새로운 메서드를 추가하며 기존 구현체 코드를 깨뜨리지 않기 위한 목적
: 불필요한 디폴트 메서드 남용은 코드 복잡도를 높임
: 새 메서드가 필요하고 기존 구현 클래스가 많지 않다면 원칙적으로 각각 구현하거나 추상 메서드를 추가하는 것을 고려
2. 인터페이스는 여전히 추상화의 역할
: 디폴트 메서드를 통해 인터페이스에 로직을 넣을 수 있더라도 가능한 한 로직은 구현 클래스나 별도 클래스에 두고, 인터페이스는 계약의 역할에 충실한 것이 좋음
: 디폴트 메서드는 어디까지나 하위 호환을 위한 기능으로 공통으로 쓰기 쉬운 간단한 로직을 제공하는 정도가 이상적
3. 다중 상속 문제
: 하나의 클래스가 여러 인터페이스를 동시에 구현할 때, 서로 다른 인터페이스에 동일한 시그니처의 디폴트 메서드 존재 시 충돌이 일어남
: 이 경우 구현 클래스에서 반드시 메서드를 재정의해야 함
interface A {
default void hello() {
System.out.println("Hello from A");
}
}
interface B {
default void hello() {
System.out.println("Hello from B");
}
}
public class MyClass implements A, B {
@Override
public void hello() {
// 반드시 충돌을 해결해야 함
// 1. 직접 구현
// 2. A.super.hello();
// 3. B.super.hello();
}
}
4. 디폴트 메서드에 상태를 두지 않기
: 인터페이스는 일반적으로 상태 없이 동작만 정의하는 추상화 계층
: 인스턴스 변수를 활용하거나 여러 차례 호출 시 상태에 따라 동작이 달라지는 로직들은 추상 클래스로 옮기는 것이 더 적절
'인프런 > 김영한의 실전 자바 - 고급 3편, 람다, 스트림, 함수형 프로그래밍' 카테고리의 다른 글
| [인프런] 김영한의 실전 자바 - 고급 3편, 람다, 스트림, 함수형 프로그래밍 / 13. 함수형 프로그래밍 (1) | 2025.07.06 |
|---|---|
| [인프런] 김영한의 실전 자바 - 고급 3편, 람다, 스트림, 함수형 프로그래밍 / 12. 병렬 스트림 (0) | 2025.07.03 |
| [인프런] 김영한의 실전 자바 - 고급 3편, 람다, 스트림, 함수형 프로그래밍 / 10. Optional (1) | 2025.06.25 |
| [인프런] 김영한의 실전 자바 - 고급 3편, 람다, 스트림, 함수형 프로그래밍 / 9. 스트림 API 3 - 컬렉터 (0) | 2025.06.23 |
| [인프런] 김영한의 실전 자바 - 고급 3편, 람다, 스트림, 함수형 프로그래밍 / 9. 스트림 API 2 - 기능 (0) | 2025.06.17 |