섹션 2. 인터페이스의 변화
인터페이스 기본 메소드와 스태틱 메소드
기본 메소드 (Default Methods)
- 인터페이스에 메소드 선언이 아니라 구현체를 제공하는 방법
- 해당 인터페이스를 구현한 클래스를 깨트리지 않고 새 기능을 추가할 수 있다.
- 기본 메소드는 구현체가 모르게 추가된 기능으로 그만큼 리스크가 있다.
- 컴파일 에러는 아니지만 구현체에 따라 런타임 에러가 발생할 수 있다.
- 반드시 문서화 할 것. (@implSpec 자바독 태그 사용)
Object가 제공하는 기능 (equals, hasCode)는 기본 메소드로 제공할 수 없으므로 구현체가 재정의해야 한다.
- 본인이 수정할 수 있는 인터페이스에만 기본 메소드를 제공할 수 있다.
- 인터페이스를 상속받는 인터페이스에서 다시 추상 메소드로 변경할 수 있다.
- 상속받은 인터페이스에서 기본 메소드를 추상 메소드로 변경할 경우 이 인터페이스를 상속받는 구현체는 전부 메소드를 재정의해야 한다.
- 인터페이스 구현체가 재정의 할 수도 있다.
스태틱 메소드
- 해당 타입 관련 헬퍼 또는 유틸리티 메소드를 제공할 때 인터페이스에 스태틱 메소드를 제공할 수 있다.
예제
Foo.java
public interface Foo {
void printName();
/**
* @implSpec
* 이 구현체는 getName()으로 가져온 문자열을 대문자로 바꿔 출력한다.
* */
default void printNameUpperCase() { // Default Method
System.out.println(getName().toUpperCase());
}
String getName();
static void printAnything() { // Static Method
System.out.println("Foo");
}
}
Bar.java - 1
public interface Bar extends Foo{
void printNameUpperCase(); // 이 인터페이스를 상속받는 클래스는 메소드를 재구현해야 함
}
Bar.java - 2
public interface Bar{
/**
* @implSpec
* 이 구현체는 getName()으로 가져온 문자열을 대문자로 바꿔 출력한다.
* */
default void printNameUpperCase() {
System.out.println("BAR");
}
}
DefaultFoo.java
public class DefaultFoo implements Foo {
// public class DefaultFoo implements Foo, Bar { 2번째 Bar를 상속받을 경우 컴파일 에러 발생 (Diamond Problem)
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;
}
}
Practice1.java
public class Practice1 {
public static void main(String[] args) {
Foo practice1 = new DefaultFoo("melt");
practice1.printName(); // melt
practice1.printNameUpperCase(); // MELT
Foo.printAnything(); // Foo
}
}
자바 8 API의 기본 메소드와 스태틱 메소드
Iterable의 기본 메소드
BaseCode
import java.util.ArrayList;
import java.util.List;
import java.util.Spliterator;
public class Practice2 {
public static void main(String[] args) {
List<String> name = new ArrayList<>();
name.add("MELT");
name.add("keesun");
name.add("melodist");
name.add("toby");
forEach()
name.forEach(System.out::println); // method reference
// 위의 코드는 아래 코드와 똑같이 동작한다.
for (String n: name) {
System.out.println(n);
}
spliterator()
: stream()과 조합하여 병렬 처리에 이용
Spliterator<String> spliterator = name.spliterator();
Spliterator<String> spliterator1 = spliterator.trySplit();
while (spliterator.tryAdvance(System.out::println));
System.out.println("============");
while (spliterator1.tryAdvance(System.out::println));
Collection의 기본 메소드
● stream()
/ parallelStream()
long k = name.stream().map(String::toUpperCase)
.filter(s -> s.startsWith("K"))
.count();
System.out.println(k); // 1
● removeIf(Predicate)
name.removeIf(s -> s.startsWith("k")); // name = ["MELT, melodist, toby"]
Comparator의 기본 메소드 및 스태틱 메소드
● reversed()
Comparator<String> compareToIgnoreCase = String::compareToIgnoreCase;
name.sort(compareToIgnoreCase.reversed());
name.forEach(System.out::println);
/*
toby
MELT
melodist
keesun
*/
● thenComparing()
● static reverseOrder()
/ naturalOrder()
● static nullsFirst()
/ nullsLast()
● static comparing()
API 라이브러리의 변화
Java 8 이전에는…
Interface : 메소드 a(), b(), c()를 모두 제공하는 인터페이스
AbstractClass : Abstract method (UML에서 이탤릭체로 표현)를 제공하는 추상 클래스
ClassA, ClassB, ClassC : 하나의 메소드만 상속받는 실제 클래스
Java 8 이전에는 클래스가 인터페이스를 상속받으면 인터페이스가 제공하는 메소드를 모두 구현해야 하기 때문에 클래스가 원하는 함수만 상속받을 수 있도록 인터페이스의 모든 메소드를 추상 메소드로 구현한 추상 클래스를 제공하였다.
Java 8 이후
추상 클래스를 상속받지 않고 기본 메소드를 사용하는 인터페이스를 직접 구현한다.