기본 메소드 (Default Method)
- 인터페이스에 메소드 선언이 아니라 구현체를 제공하는 방법
- 해당 인터페이스를 구현한 클래스를 깨뜨리지 않고 새 기능을 추가할 수 있다.
- 기본 메소드는 구현체가 모르게 추가된 기능으로 리스크가 있다.
- 컴파일 에러는 아니지만 구현체에 따라 런타임 에러가 발생할 수 있다.
- 반드시 문서화 할 것 (@implSpec 자바독 태그 사용)
- Object가 제공하는 기능 (equals, hashCode)는 기본 메소드로 제공할 수 없다.
- 인터페이스를 상속받는 인터페이스에서 다시 추상메소드로 변경할 수 있다.
- 인터페이스 구현체가 재정의 할 수도 있다.
public interface Foo5 {
void printName();
/*
@ImplSpec
이 구현체는 getName()으로 가져와서 문자열을 대문자로 바꿔 출력한다
*/
default void printNameUpperCase() {
System.out.println(getName().toUpperCase());
}
// 내가 만든 인터페이스에만 쓸 수 있다. 이미 제공된 인터페이스에는 추가 불가능
// default String toString() {} // 오류
String toString();
String getName();
static void printAnyThing() {
System.out.println("Foo");
}
}
Object가 제공하는 toString()은 기본 메소드로 재정의 할 경우 에러가 발생하지만 추상 메소드로 바꾸는건 가능하다.
public interface Bar extends Foo5{
void printNameUpperCase();
// Foo5에서 제공하는 기본 구현체를 제공하고 싶지 않은 경우 추상 클래스로 오버라이딩
// -> Bar를 상속받는 클래스가 재정의하면됨
}
Bar 인터페이스는 Foo5 인터페이스를 상속받아 기본 메소드로 선언된 printNameUpperCase()를 다시 추상 메소드로 선언하였다. 따라서 Bar의 구현클래스는 printNameUpperCase()를 재정의 해줘야 한다.
public class DefaultFoo implements Foo5 {
String name;
public DefaultFoo(String name) {
this.name = name;
}
@Override
public void printName() {
System.out.println(this.name);
}
@Override
public String getName() {
return this.name;
}
public static void main(String[] args) {
Foo5 foo5 = new DefaultFoo("Kang");
foo5.printName(); // Kang
foo5.printNameUpperCase(); // KANG
}
}
두개의 인터페이스를 상속받는 클래스
public interface Foo5 {
void printName();
default void printNameUpperCase() {
System.out.println(getName().toUpperCase());
}
String getName();
}
public interface Bar2 {
default void printNameUpperCase() {
System.out.println("Bar");
}
}
두 인터페이스는 printNameUpperCase() 라는 기본 메소드를 둘다 가지고 있다.
public class DefaultFoo implements Foo5, Bar2 {
String name;
public DefaultFoo(String name) {
this.name = name;
}
@Override
public void printName() {
System.out.println(this.name);
}
@Override
public void printNameUpperCase() {
System.out.println(this.getName().toUpperCase());
} // 재정의 하지 않으면 Foo5와 Bar2가 둘다 같은 이름으로 기본 메소드를 갖고 있기 때문에 에러가 발생한다.
@Override
public String getName() {
return this.name;
}
public static void main(String[] args) {
Foo5 foo5 = new DefaultFoo("Kang");
foo5.printName();
foo5.printNameUpperCase();
}
}
그리고 DefaultFoo는 두 인터페이스를 동시에 상속받는다. 이때 DefaultFoo는 printNameUpperCase() 메소드가 어느 인터페이스에서 받아야할지 알 수 없는 상태가 되어 컴파일 에러가 난다. 이를 해결하기 위해 중복되는 메소드는 반드시 재정의 해줘야 한다.
스태틱 메소드 (Static Method)
- 해당 타입 관련 헬퍼 또는 유틸리티 메소드를 제공할 때 인터페이스에 스태틱 메소드를 제공할 수 있다.
public interface Foo5 {
void printName();
default void printNameUpperCase() {
System.out.println(getName().toUpperCase());
}
String toString();
String getName();
// static method
static void printAnyThing() {
System.out.println("Foo");
}
}
public static void main(String[] args) {
Foo5.printAnyThing(); // Foo
}
스태틱 메소드를 만들게 된 경우 별도의 객체 생성없이 바로 메소드를 사용할 수 있다.
대표적으로 내가 알고리즘 문제를 풀때 사용하는 Math 인터페이스가 있다.
int max = Math.max(3, 4);
주어진 두개의 수의 큰값을 얻기 위해 Math 인터페이스의 스태틱 메소드 max()를 사용한 모습이다. max() 메소드에 들어가보면 다음과 같이 static으로 메소드가 정의된 모습을 볼 수 있다.
이처럼 자바는 여러가지 유틸리티 메소드를 제공할 때 스태틱 메소드를 사용한다.
자바 8 API의 기본 메소드와 스태틱 메소드
List<String> names = new ArrayList<>();
names.add("kang");
names.add("min");
names.add("hyeong");
Iterable의 기본 메소드
- forEach()
- spliterator()
names.forEach(System.out::println);
Collection의 기본 메소드
- stream() / parallelStream()
- removeIf(Predicate)
- spliterator()
List<String> k = names.stream().map(String::toUpperCase)
.filter(s -> s.startsWith("K"))
.collect(Collectors.toList());
System.out.println(k);
names.removeIf(s -> s.startsWith("k"));
names.forEach(System.out::println);
Spliterator<String> spliterator = names.spliterator();
Spliterator<String> stringSpliterator = spliterator.trySplit();
while (spliterator.tryAdvance(System.out::println)) ;
System.out.println("=========================");
while (stringSpliterator.tryAdvance(System.out::println)) ;
Comparator의 기본 메소드 및 스태틱 메소드
- reversed()
- thenComparing()
- static reverseOrder() / naturalOrder()
- static nullsFirst() / nullsLast()
- static comparing()
Comparator<String> comparator = String::compareToIgnoreCase;
names.sort(comparator.reversed());
System.out.println(names);
인터페이스 사용의 변화
인터페이스가 3개의 추상 메소드를 갖고있고 이를 추상 클래스가 받아 default 메소드로 바꾼 다음 A, B, C 클래스에서 이를 상속받아 사용하고 있다. 추상 클래스에 메소드는 기본 함수가 들어갈 수도 있고 추상메소드가 들어갈 수도 있다. 이는 사용자가 필요에 따라서 직접 재정의 해서 사용하도록 편의성을 제공한 형태이다.
하지만 Java8로 오면서 인터페이스에서 기본 메소드를 정의할 수 있게 되었다. 따라서 더이상 추상 클래스를 선언해서 위와 같은 과정을 반복 할 필요가 없게 되었다. 이를 잘 나타내는 것중 하나가 WebMvcConfigurer 인터페이스이다.
WebMvcConfigurerAdapter 라는 추상 클래스는 WebMvcConfigurer 인터페이스를 implements한 추상 클래스다. 과거에는 인터페이스에 기본 메소드를 정의할 수 없었기 때문에 이처럼 추상클래스를 만들어 메소드를 정의해주었다. 하지만 이는 더이상 불필요하기 때문에 Deprecated 되었다.
참고자료
더 자바, Java 8 - 인프런 | 강의
자바 8에 추가된 기능들은 자바가 제공하는 API는 물론이고 스프링 같은 제 3의 라이브러리 및 프레임워크에서도 널리 사용되고 있습니다. 이 시대의 자바 개발자라면 반드시 알아야 합니다. 이
www.inflearn.com
'java' 카테고리의 다른 글
Design Patterns - Singleton (0) | 2021.11.11 |
---|---|
함수형 인터페이스를 이용한 Builder (0) | 2021.05.29 |
더 자바(java 8) - CompletableFuture (0) | 2021.05.26 |
더 자바(java 8) - Stream, Optional, Date (0) | 2021.05.19 |
더 자바(java 8) - 함수형 인터페이스와 람다 (0) | 2021.05.10 |
댓글