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