자바가 람다를 지원하면서 상위 클래스의 기본 메서드를 재정의하는 템플릿 메서드 패턴의 매력이 크게 줄었다.
이를 대체하는 현대적인 해법은 함수 객체를 받는 정적 팩터리나 생성자를 제공하는 것이다.
이 말은, 함수 객체를 매개변수로 받는 생성자와 메서드를 더 많이 만들어야 한다는 의미다. 이때 함수형 매개변수 타입을 올바르게 선택해야 한다.
필요한 용도에 맞는게 있다면, 직접 구현하지 말고 표준 함수형 인터페이스를 활용하자.
java.util.function
패키지에는 다양한 용도의 표준 함수형 인터페이스가 담겨있다. (총 43개)
그 중 기본 인터페이스 6가지는 다음과 같다.
인터페이스 | 함수 시그니처 | 예시 |
---|---|---|
UnaryOperator<T> |
T apply(T t) |
String::toLowerCase |
BinaryOperator<T> |
T apply(T t1, T t2) |
BigInteger::add |
Predicate<T> |
boolean test(T t) |
Collection::isEmpty |
Function<T,R> |
R apply(T t) |
Arrays::asList |
Supplier<T> |
T get() |
Instant::now |
Consumer<T> |
void accept(T t) |
System.out::println |
기본 인터페이스는 기본 타입인 int
, long
, double
용으로 각 3개씩 변형이 있다.
ex) IntPredicate, LongBinaryOperator
Operator
인터페이스는 반환값과 인수의 타입이 같은 함수를 뜻한다.
인수가 1개인 UnaryOperator
와 2개인 BinaryOperator
로 나뉜다.
인터페이스 이름 | 함수 시그니처 | |
---|---|---|
1 | BinaryOperator<T> | T apply(T, T) |
2 | UnaryOperator<T> | T apply(T) |
3 | DoubleBinaryOperator | double applyAsDouble(double, double) |
4 | DoubleUnaryOperator | double applyAsDouble(double) |
5 | IntBinaryOperator | int applyAsInt(int, int) |
6 | IntUnaryOperator | int applyAsInt(int) |
7 | LongBinaryOperator | long applyAsLong(long, long) |
8 | LongUnaryOperator | long applyAsLong(long) |
Predicate
인터페이스는 인수 1개를 받아 boolean을 반환하는 함수를 뜻한다.
인터페이스 이름 | 함수 시그니처 | |
---|---|---|
1 | Predicate<T> | boolean test(T t) |
2 | BiPredicate<T, U> | boolean test(T t, U u) |
3 | DoublePredicate | boolean test(double value) |
4 | IntPredicate | boolean test(int value) |
5 | LongPredicate | boolean test(long value) |
Function
인터페이스는 인수와 반환 타입이 다른 함수를 뜻한다.
기본 타입을 반환하는 변형이 총 9가지 더 존재한다.
Function 인터페이스의 변형은 입력과 결과의 타입이 항상 다르다.
입력과 결과 타입이 모두 기본 타입이면 접두어로 SrcToResult
를 사용한다 (ex. LongToIntFunctioin).
이러한 변형들은 총 6개다.
나머지 3개는 입력이 객체 참조이고 결과가 int, long, double인 변형들이다.
앞서와 달리 입력을 매개변수화하고 접두어로 ToResult
를 사용한다 (ex. ToLongFunctioin<int[]>).
인터페이스 이름 | 함수 시그니처 | |
---|---|---|
1 | Function<T, R> | R apply(T t) |
2 | BiFunction<T, U, R> | R apply(T t, U u) |
3 | DoubleFunction<R> | R apply(double value) |
4 | IntFunction<R> | R apply(int value) |
5 | LongFunction<R> | R applyAsDouble(long value) |
6 | DoubleToIntFunction | int applyAsInt(double value) |
7 | DoubleToLongFunction | long applyAsLong(double value) |
8 | IntToDoubleFunction | double applyAsDouble(int value) |
9 | IntToLongFunction | long applyAsLong(int value) |
10 | LongToDoubleFunction | double applyAsDouble(long value) |
11 | LongToIntFunction | int applyAsInt(long value) |
12 | ToDoubleBiFunction<T, U> | double applyAsDouble(T t, U u) |
13 | ToDoubleFunction<T> | double applyAsDouble(T t) |
14 | ToIntBiFunction<T, U> | int applyAsInt(T t, U u) |
15 | ToIntFunction<T> | int applyAsInt(T t) |
16 | ToLongBiFunction<T, U> | long applyAsLong(T t, U u) |
17 | ToLongFunction<T> | long applyAsLong(T t) |
Supplier
인터페이스는 인수를 받지 않고 값을 반환(제공)하는 함수를 뜻한다.
BooleanSupplier 인터페이스는 boolean을 반환하도록 하는 Supplier의 변형이다.
인터페이스 이름 | 함수 시그니처 | |
---|---|---|
1 | Supplier<T> | T get() |
2 | BooleanSupplier | boolean getAsBoolean() |
3 | DoubleSupplier | double getAsDouble() |
4 | IntSupplier | int getAsInt() |
5 | LongSupplier | long getAsLong() |
Consumer
인터페이스는 인수를 하나 받고 반환값은 없는(특히 인수를 소비하는) 함수를 뜻한다.
인터페이스 이름 | 함수 시그니처 | |
---|---|---|
1 | Consumer<T> | void accept(T t) |
2 | BiConsumer<T,U> | void accept(T t, U u) |
3 | DoubleConsumer | void accept(double value) |
4 | IntConsumer | void accept(int value) |
5 | LongConsumer | void accept(long value) |
6 | ObjDoubleConsumer<T> | void accept(T t, double value) |
7 | ObjIntConsumer<T> | void accept(T t, int value) |
8 | ObjLongConsumer<T> | void accept(T t, long value) |
💡 주의 사항
표준 함수형 인터페이스 대부분은 기본 타입만 지원한다.
그렇다고 기본 함수형 인터페이스에 박싱된 기본 타입을 넣어 사용하지는 말자.
동작은 하지만, 계산량이 많을 때 성능이 처참히 느려질 수 있다.
대부분 상황에서는 직접 작성하는 것보다 표준 함수형 인터페이스를 사용하는 편이 낫다.
그렇지만 함수형 인터페이스를 직접 작성해야만 할 때도 있다.
물론 표준 인터페이스 중 필요한 용도에 맞는 게 없다면 직접 작성해야 한다.
그런데 구조적으로 똑같은 표준 함수형 인터페이스가 있더라도 직접 작성해야할 때도 있다.
다음 조건 중 하나 이상을 만족하는 경우, 전용 함수형 인터페이스를 구현해야 하는것 아닌지 고민해야 한다.
전용 함수형 인터페이스를 작성하기로 했다면, '인터페이스'를 작성한다는 것을 명심하고 주의해서 설계해야 한다. (아이템 21)
함수형 인터페이스에 @FunctionalInterface
애너테이션을 사용하는 이유는 프로그래머의 의도를 명시하기 위한 것으로 크게 세 가지 목적이 있다.
직접 만든 함수형 인터페이스에는 항상 @FunctionalInterface
애너테이션을 사용하자.
함수형 인터페이스를 API에서 사용할 때, 서로 다른 함수형 인터페이스를 같은 위치의 인수로 받는 메서드들을 다중 정의해서는 안 된다.
클라이언트에게 불필요한 모호함을 안겨주어, 실제로 문제가 일어나기도 한다.
📌 핵심 정리
이제 자바도 람다를 지원한다. 지금부터는 API를 설계할 때 람다도 염두에 두어야 한다는 뜻이다.
입력값과 반환값에 함수형 인터페이스 타입을 활용하라.
보통은 java.util.function 패키지의 표준 함수형 인터페이스를 사용하는 것이 가장 좋은 선택이다.
단, 흔치는 않지만 직접 새로운 함수형 인터페이스를 만들어 쓰는 편이 나을 수 있다.