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

문법식·2022년 9월 6일
0

Effective Java 3/E

목록 보기
44/52

자바가 람다를 지원하면서 API를 작성하는 모범 사례도 많이 바뀌었다. 템플릿 메서드 패턴의 매력이 크게 줄었다. 이를 대체하는 요즘 해법은 같은 효과의 함수 객체를 받는 정적 팩터리나 생성자를 제공하는 것이다. 이것을 일반화해서 말하면 함수 객체를 매개변수로 받는 생성자와 메서드를 더 많이 만들어야 한다. 이 때 함수형 매개변수 타입을 올바르게 선택해야 한다. 생성자의 경우 전달받는 함수 객체는 생성하려는 객체의 인스턴스 메서드가 아니다. 팩터리나 생성자를 호출할 때는 생성하려는 객체의 인스턴스가 아직 존재하지 않기 때문이다. 그래서 자기 자신도 함수 객체에 건네줘야 한다.
직접 정의한 함수형 인터페이스의 기능을 똑같이 제공하는 표준 함수형 인터페이스가 있다면 직접 정의 된 것을 굳이 사용할 이유는 없다. java.util.fuaction의 패키지에는 다양한 표준 함수형 인터페이스가 있다. 필요한 용도에 맞는게 있다면, 직접 구현하지 말고 표준 함수형 인터페이스를 활용해야 한다. 그러면 API가 다루는 개념의 수가 줄어들어 익히기 더 쉬워진다. 또한 표준 함수형 인터페이스들은 유용한 디폴트 메서드를 많이 제공하므로 다른 코드와의 상호운용성도 크게 좋아진다.
java.util.fuaction 패키지에는 총 43개의 인터페이스가 있다. 기본 인터페이스 6개만 기억하면 나머지를 충분히 유추해낼 수 있다. 기본 인터페이스들은 모두 참조 타입용이다.

  • Operator 인터페이스
    반환값과 인수의 타입이 같은 함수를 뜻한다.
    • UnaryOperator
      인수가 1개이다.
    • BinaryOperator
      인수가 2개이다.
  • Predicate 인터페이스
    인수를 하나 받아 boolean을 반환하는 함수를 뜻한다.
  • Function 인터페이스
    인수와 반환 타입이 다른 함수를 뜻한다.
  • Supplier 인터페이스
    인수를 받지 않고 반환(혹은 제공)하는 함수를 뜻한다.
  • Consumer 인터페이스
    인수를 하나 받고 반환하는 값은 함수를 뜻한다.

기본 함수형 인터페이스를 정리한 표다.

인터페이스함수 시그니처
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

기본인터페이스의 변형은 책을 참고하길 바란다. 변형을 다 외우기엔 많고 규칙성도 부족하기 때문이다.

표준 함수형 인터페이스 대부분은 기본 타입만 지원한다. 그렇다고 기본 함수형 인터페이스에 박싱된 기본 타입을 넣어 사용하면 안 된다. 동작은 하지만 "박싱된 기본 타입 대신 기본 타입을 사용하라"라는 아이템 61의 조언을 위배한다. 특히 계산량이 많을 때는 성능이 처참히 느려진다.
직접 작성하는 것보다 표준 함수형 인터페이스를 사용하는 편이 났다는 것을 알았다. 그렇다면 직접 작성해야 하는 경우는 언제인지 알아본다. 물론 표준 인터페이스 중 필요한 용도에 맞는 게 없다면 직접 작성해야 한다. 또한, 다음 조건 중 하나 이상을 만족한다면 전용 함수형 인터페이스를 구현해야 하는 건 아닌지 진지하게 고민해야 한다.

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

전용 함수형 인터페이스를 작성하기로 했다면, 자신이 작성하는 게 다른 것도 아닌 '인터페이스'임을 명심해야 한다. 아주 주의해서 설계해야 한다는 뜻이다.
직접 만든 함수형 인터페이스에는 @FunctionalInterface를 항상 사용해야 한다. 프로그래머의 의도를 명확히 하는 것으로 크게 세 가지 목적이 있다.

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

마지막으로, 함수형 인터페이스를 API에서 사용할 때의 주의점을 알아야 한다. 서로 다른 함수형 인터페이스를 같은 위치의 인수로 받는 메서드들을 다중 정의해서는 안 된다. 클라이언트에게 불필요한 모호함을 안겨줄 뿐이며, 이 모호함으로 인해 실제 문제가 일어나기도 한다.ExecutorServicesubmit 메서드는 Callabl<T>를 받는 것과 Runnable을 받는 것을 다중 정의했다. 그래서 올바른 메서드를 알려주기 위해 형변환해야 할 때가 이따금 생긴다. 이런 문제를 피하는 가장 쉬운 방법은 서로 다른 함수형 인터페이를 같은 위치의 인수로 사용하는 다중 정의를 피하는 것이다.

profile
백엔드

0개의 댓글