아이템 44. 표준 함수형 인터페이스를 사용하라

wisdom·2022년 9월 15일
0

Effetctive Java

목록 보기
44/80
post-thumbnail

자바가 람다를 지원하면서 상위 클래스의 기본 메서드를 재정의하는 템플릿 메서드 패턴의 매력이 크게 줄었다.
이를 대체하는 현대적인 해법은 함수 객체를 받는 정적 팩터리나 생성자를 제공하는 것이다.
이 말은, 함수 객체를 매개변수로 받는 생성자와 메서드를 더 많이 만들어야 한다는 의미다. 이때 함수형 매개변수 타입을 올바르게 선택해야 한다.

1. 표준 함수형 인터페이스

필요한 용도에 맞는게 있다면, 직접 구현하지 말고 표준 함수형 인터페이스를 활용하자.

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


1) Operator 인터페이스

Operator 인터페이스는 반환값과 인수의 타입이 같은 함수를 뜻한다.

인수가 1개인 UnaryOperator와 2개인 BinaryOperator로 나뉜다.

인터페이스 이름함수 시그니처
1BinaryOperator<T>T apply(T, T)
2UnaryOperator<T>T apply(T)
3DoubleBinaryOperatordouble applyAsDouble(double, double)
4DoubleUnaryOperatordouble applyAsDouble(double)
5IntBinaryOperatorint applyAsInt(int, int)
6IntUnaryOperatorint applyAsInt(int)
7LongBinaryOperatorlong applyAsLong(long, long)
8LongUnaryOperatorlong applyAsLong(long)

2) Predicate 인터페이스

Predicate 인터페이스는 인수 1개를 받아 boolean을 반환하는 함수를 뜻한다.

인터페이스 이름함수 시그니처
1Predicate<T>boolean test(T t)
2BiPredicate<T, U>boolean test(T t, U u)
3DoublePredicateboolean test(double value)
4IntPredicateboolean test(int value)
5LongPredicateboolean test(long value)

3) Function 인터페이스

Function 인터페이스는 인수와 반환 타입이 다른 함수를 뜻한다.

기본 타입을 반환하는 변형이 총 9가지 더 존재한다.

Function 인터페이스의 변형은 입력과 결과의 타입이 항상 다르다.
입력과 결과 타입이 모두 기본 타입이면 접두어로 SrcToResult 를 사용한다 (ex. LongToIntFunctioin).
이러한 변형들은 총 6개다.

나머지 3개는 입력이 객체 참조이고 결과가 int, long, double인 변형들이다.
앞서와 달리 입력을 매개변수화하고 접두어로 ToResult 를 사용한다 (ex. ToLongFunctioin<int[]>).

인터페이스 이름함수 시그니처
1Function<T, R>R apply(T t)
2BiFunction<T, U, R>R apply(T t, U u)
3DoubleFunction<R>R apply(double value)
4IntFunction<R>R apply(int value)
5LongFunction<R>R applyAsDouble(long value)
6DoubleToIntFunctionint applyAsInt(double value)
7DoubleToLongFunctionlong applyAsLong(double value)
8IntToDoubleFunctiondouble applyAsDouble(int value)
9IntToLongFunctionlong applyAsLong(int value)
10LongToDoubleFunctiondouble applyAsDouble(long value)
11LongToIntFunctionint applyAsInt(long value)
12ToDoubleBiFunction<T, U>double applyAsDouble(T t, U u)
13ToDoubleFunction<T>double applyAsDouble(T t)
14ToIntBiFunction<T, U>int applyAsInt(T t, U u)
15ToIntFunction<T>int applyAsInt(T t)
16ToLongBiFunction<T, U>long applyAsLong(T t, U u)
17ToLongFunction<T>long applyAsLong(T t)

4) Supplier 인터페이스

Supplier 인터페이스는 인수를 받지 않고 값을 반환(제공)하는 함수를 뜻한다.

BooleanSupplier 인터페이스는 boolean을 반환하도록 하는 Supplier의 변형이다.

인터페이스 이름함수 시그니처
1Supplier<T>T get()
2BooleanSupplierboolean getAsBoolean()
3DoubleSupplierdouble getAsDouble()
4IntSupplierint getAsInt()
5LongSupplierlong getAsLong()

5) Consumer 인터페이스

Consumer 인터페이스는 인수를 하나 받고 반환값은 없는(특히 인수를 소비하는) 함수를 뜻한다.

인터페이스 이름함수 시그니처
1Consumer<T>void accept(T t)
2BiConsumer<T,U>void accept(T t, U u)
3DoubleConsumervoid accept(double value)
4IntConsumervoid accept(int value)
5LongConsumervoid accept(long value)
6ObjDoubleConsumer<T>void accept(T t, double value)
7ObjIntConsumer<T>void accept(T t, int value)
8ObjLongConsumer<T>void accept(T t, long value)

💡 주의 사항

표준 함수형 인터페이스 대부분은 기본 타입만 지원한다.
그렇다고 기본 함수형 인터페이스에 박싱된 기본 타입을 넣어 사용하지는 말자.
동작은 하지만, 계산량이 많을 때 성능이 처참히 느려질 수 있다.


2. 함수형 인터페이스 직접 작성

대부분 상황에서는 직접 작성하는 것보다 표준 함수형 인터페이스를 사용하는 편이 낫다.

그렇지만 함수형 인터페이스를 직접 작성해야만 할 때도 있다.

물론 표준 인터페이스 중 필요한 용도에 맞는 게 없다면 직접 작성해야 한다.

그런데 구조적으로 똑같은 표준 함수형 인터페이스가 있더라도 직접 작성해야할 때도 있다.
다음 조건 중 하나 이상을 만족하는 경우, 전용 함수형 인터페이스를 구현해야 하는것 아닌지 고민해야 한다.

  • 자주 쓰이며, 이름 자체가 용도를 명확히 설명해준다.
  • 반드시 따라야 하는 규칙이 있다.
  • 유용한 디폴트 메서드를 제공할 수 있다.

전용 함수형 인터페이스를 작성하기로 했다면, '인터페이스'를 작성한다는 것을 명심하고 주의해서 설계해야 한다. (아이템 21)

@FunctionalInterface 애너테이션

함수형 인터페이스에 @FunctionalInterface 애너테이션을 사용하는 이유는 프로그래머의 의도를 명시하기 위한 것으로 크게 세 가지 목적이 있다.

  1. 해당 클래스의 코드나 설명 문서를 읽을 이에게 그 인터페이스가 람다용으로 설계된 것임을 알려준다.
  2. 해당 인터페이스가 추상 메서드를 오직 하나만 가지고 있어야 컴파일되도록 해준다.
  3. 유지보수 과정에서 누군가 실수로 메서드를 추가하지 못하게 막아준다. (2의 결과이기도 하다)

직접 만든 함수형 인터페이스에는 항상 @FunctionalInterface 애너테이션을 사용하자.


주의점

함수형 인터페이스를 API에서 사용할 때, 서로 다른 함수형 인터페이스를 같은 위치의 인수로 받는 메서드들을 다중 정의해서는 안 된다.

클라이언트에게 불필요한 모호함을 안겨주어, 실제로 문제가 일어나기도 한다.


📌 핵심 정리

이제 자바도 람다를 지원한다. 지금부터는 API를 설계할 때 람다도 염두에 두어야 한다는 뜻이다.
입력값과 반환값에 함수형 인터페이스 타입을 활용하라.
보통은 java.util.function 패키지의 표준 함수형 인터페이스를 사용하는 것이 가장 좋은 선택이다.
단, 흔치는 않지만 직접 새로운 함수형 인터페이스를 만들어 쓰는 편이 나을 수 있다.

profile
백엔드 개발자

0개의 댓글