티스토리 뷰
728x90
반응형
서론
- 자바에서는 인터페이스를 구현하는 구현 클래스에서는 해당 인터페이스의 메서드를 모두 구현해야 합니다. 하지만 자바 8이후 부터는
인터페이스의 static method, default method를 통해 구현체에서 메서드 구현을 생략할 수 있습니다. - 디폴트 메서드의 역할은 기존 인터페이스를 구현하는 구현 클래스는 기본적으로 디폴트 메서드를 상속받게 되며, 구현 클래스에서 따로 구현하지 않아도 됩니다.
변화하는 API
💡 릴리즈 1.0 버전의 FileUtils 인터페이스
- 버전 1에서는 기본적으로 4가지의 기능을 제공하고 있고 각 라이브러리 사용자는 해당 인터페이스를 구현해 사용을 하고 있었습니다.
하지만 시간이 흐르자 4가지 기능만으로 해당 인터페이스를 사용하는 사용자들의 불만이 터져나왔습니다. 결국 라이브러리 제공자는 몇 가지 추가 기능을 제공하기로 마음을 먹었습니다.
public interface FileUtils {
void upload();
void download();
void resizing();
String getFileName();
}
// FileUtils를 구현해 사용하는 사용자들
public class FileUtilsImpl implements FileUtils {
...
}
💡 릴리즈 2.0 버전의 FileUtils 인터페이스
- 릴리즈 2.0에서는 두가지의 기능이 추가되었습니다. 하지만 기존 릴리즈 1.0을 구현하고 있는 수많은 사용자들에게 문제가 발생합니다. 바로 해당 라이브러리를 사용하고 있는 사용자들은 두 메서드를 구현하고 있지 않기 때문에 재 컴파일시 에러가 발생하게 됩니다.
그럼 대체 어떻게 라이브러리 설계자는 이러한 걱정을 없애고 추가 기능을 삽입할 수 있을까요?
// 릴리즈 2.0에 추가된 기능들
public interface FileUtils {
void upload();
void download();
void resizing();
String getFileName();
// 2.0 버전에 추가된 메서드들
int setMaxUploadCount(int maxCount); // 최대 업로드 개수
boolean accessExtensionSetting(List<String> extensions); // 허용된 확장자 설정
}
😱 사용자가 겪는 문제들
- 기존 FileUtils 인터페이스를 수정하게 되면 해당 인터페이스를 구현한 구현 클래스에서는 메서드를 추가해야 합니다. 하지만 바이너리 호환성으로 인해 잠시나마 에러를 피해갈 수 있겠지만 그것은 정말 잠시일 뿐 이므로 결국 수정을 해야합니다.
바이너리 호환성이란?
- 뭔가를 변경한 이후에도 에러 없이 기존 바이너리가 실행될 수 있는 상황을 바이너리 호환성이라 합니다.
- 인터페이스에 새로운 메서드를 추가했을 경우 추가된 메서드를 호출하지 않는 이상 문제가 발생하지 않습니다.
소스 호환성이란?
- 코드를 수정해도 기존 프로그램을 성공적으로 재컴파일할 수 있음을 의미합니다.
- 인터페이스에 메서드를 추가하면(default, static 메서드 제외) 소스 호환성이 아닙니다. 추가한 메서드를 구현하도록 구현 클래스를 고처야 하기 때문입니다.
동작 호환성이란?
- 코드를 변경한 후에 같은 입력값이 주어지면 프로그램이 같은 동작을 실행한다는 의미입니다.
디폴트 메서드란 무엇인가?
- 위에서 FileUtils 인터페이스에 새로운 메서드를 추가하면 발생하는 문제점을 알아보았습니다. 그럼 어떻게 해결할 수 있을지 알아보겠습니다.
- 자바 8에서는 호환성을 유지하면서 API를 변경할 수 있도록 새로운 기능인 디폴트 메서드를 제공하고 있습니다.
- default 메서드를 추가하면 구현 클래스에서는 아무런 변경없이 해당 메서드를 상속받게 됩니다. 또한 인터페이스에 디폴트 메서드를 추가하면 소스 호환성이 유지됩니다.
public interface FileUtils {
void upload();
void download();
void resizing();
String getFileName();
// 2.0 버전에 추가된 메서드들
default int setMaxUploadCount(int maxCount) {
// 로직
}
default boolean accessExtensionSetting(List<String> extensions) {
// 로직
}
}
💡 추상 클래스와 인터페이스의 차이
- 클래스는 하나의 추상 클래스만을 상속 받을 수 있지만 인터페이스는 여러 개를 구현할 수 있습니다.
- 추상 클래스는 인스턴스 변수를 가질 수 있지만 인터페이스는 인스턴스 변수를 가질 수 없습니다.
해석 규칙
- 여러 인터페이스로부터 같은 시그니처를 갖는 디폴트 메서드를 상속받는 상황이 발생할 수도 있습니다. 자바 8에서는 이러한 문제에 대한 해결 규칙을 제공하고 있습니다.
💡 3가지 규칙
- 클래스가 항상 이깁니다. 클래스나 슈퍼 클래스에서 정의한 메서드가 디폴트 메서드보다 우선권을 갖습니다.
- 1번 규칙 이외의 상황에서는 서브 인터페이스가 이깁니다. 상속 관계를 갖는 인터페이스에서 같은 시그니처를 갖는 메서드를 정의할
때는 서브 인터페이스가 이깁니다. 즉, B가 A를 상속 받는다면 B가 이깁니다. - 여전히 디폴트 메서드의 우선 순위가 결정되지 않았다면 여러 인터페이스를 상속받는 클래스가 명시적으로 디폴트 메서드를 재정의해야 합니다.
예제 1
- C에서는 B, A를 implements하고 있고 B는 A를 extends하고 있는 상황에서는 어떤게 출력될까요?
바로 B 인터페이스의 디폴트 메서드가 출력됩니다. 그 이유는 3가지 규칙 중 두 번째인 서브 인터페이스가 우선권을 가지게 되기 때문입니다.
public interface A {
default void hello() {
System.out.println("Hello A");
}
}
public interface B extends A {
default void hello() {
System.out.println("Hello B");
}
}
public class C implements B, A {
public static void main(String[] args) {
C c = new C();
c.hello(); // 어떤게 호출될까? -> Hello B
}
}
예제 2
- D는 C를 extends하고 있는 동시에 B, A를 implements하고 있는 상황에서는 어떤게 출력될까요?
바로 C 클래스에 있는 메서드가 호출됩니다. 그 이유는 3가지 규칙 중 첫 번째인 클래스가 항상 이기기 때문입니다.
public interface A {
default void hello() {
System.out.println("Hello A");
}
}
public interface B extends A {
default void hello() {
System.out.println("Hello B");
}
}
public class C implements A {
public void hello() {
System.out.println("Hello C");
}
}
public class D extends C implements B, A {
public static void main(String[] args) {
D d = new D();
d.hello(); // 어떤게 호출될까? -> Hello C
}
}
예제 3
- 그렇다면 D 클래스는 C를 상속받고 있지만 C에는 아무 메서드가 없고 단순히 A 인터페이스를 implements하고 있는 상황에서는 어떤 메서드가 호출될까요?
제 생각에는 클래스가 우선권을 가지니 그래도 C 클래스에게 먼저 도달하고 C 클래스는 A 인터페이스를 implements 하고 있으니
A 인터페이스의 메서드가 호출되지 않을까? 생각을 했지만 결과는 B 인터페이스의 메서드가 호출되었습니다.
public interface A {
default void hello() {
System.out.println("Hello A");
}
}
public interface B extends A {
default void hello() {
System.out.println("Hello B");
}
}
public class C implements A {
}
public class D extends C implements B, A {
public static void main(String[] args) {
D d = new D();
d.hello(); // 어떤게 호출될까? -> Hello B
}
}
예제 4
- A, B 인터페이스가 상속 관계가 아니고 C 클래스에서 A, B를 둘다 implements를 하고 있는 경우 C 클래스에서는 어떤 메서드를 호출해야 하는지 모르기 때문에 컴파일 에러가 발생하게 됩니다. 그렇기 때문에 명시적으로 선언해야 합니다. 아래와 같은 경우는
세가지 규칙 중 마지막 규칙에 해당됩니다.
public interface A {
default void hello() {
System.out.println("Hello A");
}
}
public interface B {
default void hello() {
System.out.println("Hello B");
}
}
// 컴파일 에러 발생, A, B 인터페이스의 시그니처 메서드가 동일하여 명시적으로 선언해야 함
public class C implements A, B {
// 문제 발생
}
// 명시적으로 선언하여 문제 해결
public class C implements A, B {
@Override
public void hello() {
A.super.hello(); // 명시적으로 선언
}
}
728x90
반응형
'스터디 > 모던 자바 인 액션' 카테고리의 다른 글
모던 자바 인 액션 - 17장 리액티브 프로그래밍 (0) | 2022.09.24 |
---|---|
모던 자바 인 액션 - 16장 CompletableFuture: 안정적 비동기 프로그래밍 (2) | 2022.09.20 |
모던 자바 인 액션 - 11장 null 대신 Optional 클래스 (0) | 2022.09.15 |
모던 자바 인 액션 - 9장 리펙터링, 테스팅, 디버깅 (0) | 2022.09.11 |
모던 자바 인 액션 - 8장 컬렉션 API 개선 (0) | 2022.09.10 |
공지사항
최근에 올라온 글
최근에 달린 댓글
- Total
- Today
- Yesterday
TAG
- 트랜잭셔널 아웃박스 패턴 스프링 부트 예제
- service based architecture
- microkernel architecture
- redis 대기열 구현
- spring boot redisson 분산락 구현
- spring boot redisson sorted set
- transactional outbox pattern
- redis sorted set
- 자바 백엔드 개발자 추천 도서
- spring boot redisson destributed lock
- 람다 표현식
- 서비스 기반 아키텍처
- pipeline architecture
- spring boot excel download paging
- spring boot excel download oom
- JDK Dynamic Proxy와 CGLIB의 차이
- 공간 기반 아키텍처
- polling publisher spring boot
- 트랜잭셔널 아웃박스 패턴 스프링부트
- pipe and filter architecture
- transactional outbox pattern spring boot
- @ControllerAdvice
- spring boot 엑셀 다운로드
- redis sorted set으로 대기열 구현
- 레이어드 아키텍처란
- spring boot redis 대기열 구현
- java userThread와 DaemonThread
- java ThreadLocal
- spring boot poi excel download
- space based architecture
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 |
글 보관함