Home [Effective Java] Item44. 표준 함수형 인터페이스를 사용하라!
Post
Cancel

[Effective Java] Item44. 표준 함수형 인터페이스를 사용하라!

Item44. 표준 함수형 인터페이스를 사용하라!

  • 자바라 람다를 지원하게 되면서 API 작성의 모범 사례가 크게 변했다.
    • 예를 들어 상위 클래스의 기본 메서드를 재정의해 원하는 동작을 구현하는 템플릿 메서드 패턴의 매력이 크게 줄었다.
      • 이를 대체하는 현대적 방법? 같은 효과의 함수 객체를 받는 정적 팩터리나 생성자를 제공하는 것
      • 함수 객체를 파라미터로 받는 생성자, 메서드가 증가했다.
      • 이러한 경우를 대비해 올바른 함수형 매개변수 타입을 선택해야 한다.

직접 구현하지 말고 표준 함수형 인터페이스를 활용하라

예시

1
2
3
protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
    return size() > 100;
}
  • removeEldestEntry를 재정의해, 마치 캐시처럼 동작하게 구현할 수 있다.
    • 맵의 원소가 100개가 될 때까지는 커지다가, 그 이상이 되면 오래된 원소를 제거해나간다.
  • 물론 잘 동작한다. 하지만 람다를 사용하면 훨씬 잘 해낼 수 있다.
    • 오늘날 다시 구현한다면 함수 객체를 받는 정적 팩터리나 생성자를 제공했을 것이다.
1
2
3
4
@FuntionalInterface 
interface EldestEntryRemovalFunction<K, V> {
    boolean remove(Map<K, V> map, Map.Entry<K, V> eldest);
}
  • 팩터리나 생성자에 넘기는 함수 객체는 인스턴스 메서드가 아니라서 팩터리나 생성자 호출 시 맵의 인스턴스가 존재하지 않는 문제가 있다.
    • 따라서 맵은 자기 자신도 함수 객체에 건네준다.
  • 이 인터페이스도 잘 동작한다. 그런데 굳이 사용하지 않아도 된다.
    • Why? 이미 잘 구현된 표준 함수형 인터페이스가 있기 때문!

표준 함수형 인터페이스

  • java.util.function 패키지에 다양한 용도의 표준 함수형 인터페이스가 담겨있다.
    • 총 43개의 인터페이스가 담겨 있다.
    • 기본 인터페이스 6개 정도는 기억해 두었다가 사용하자. 그럼 나머지는 충분히 유추 가능할 것이다.
      • 기본 인터페이스는 모두 참조 타입용이다.
  • 필요한 용도에 맞는 게 있다면, 직접 구현하지 말고 표준 함수형 인터페이스를 활용하자.
  • 표준 함수형 인터페이스들은 유용한 디폴트 메서드를 많이 제공한다. 그러므로 다른 코드와의 상호운용성도 크게 좋아질 것이다.

(표준) 기본 함수형 인터페이스

인터페이스함수 시그니처의미
UnaryOperatorT apply(T t)반환 타입 == 인수 타입, 인수는 1개String::toLowerCase
BinaryOperatorT apply(T t1, T t2)반환 타입 == 인수 타입, 인수는 2개BigInteger::add
Predicateboolean test(T t)boolean 반환, 인수는 1개Collection::isEmpty
Function<T, R>R apply(T t)반환 타입 != 인수 타입, 인수는 1개Arrays::asList
SupplierT get()값을 반환(혹은 제공)하는 함수, 인수 없음Instant::now
Consumervoid accept(T t)반환 없음, 인수는 1개System.out::println
  • 대부분 기본 타입인 int, long, double만 지원한다.
    • 그렇다고 기본 함수형 인터페이스에 박싱된 기본 타입을 넣어 사용하지는 말자. 어길 시 성능이 처참해질 수 있음을 명심하자.
      • 아이템 61: 박싱된 기본 타입 대신 기본 타입을 사용하라.

직접 구현해야 하는 경우

  • 표준 인터페이스 중 필요한 용도에 맞는 게 없는 경우
    • 예 : 매개변수 3개 받는 경우, 검사 예외를 던지는 경우 등..
  • 구조적으로 똑같더라도 직접 구현한 함수형 인터페이스를 사용하는 경우
    1. API에서 굉장히 자주 사용되는데, 지금의 이름이 그 용도를 아주 훌륭히 설명해주는 경우
    2. 구현하는 쪽에서 반드시 지켜야 할 규약을 담고 있는 경우
    3. 비교자들을 변환하고 조합해주는 유용한 디폴트 메서드들을 듬뿍 담고 있는 경우

예시 - Comparator

  • Comparator 인터페이스는 구조족으로는 ToIntBiFunction<T, U>와 동일하다.
    • 심지어 Comparator를 추가하기 전 ToIntBiFunction<T, U>가 존재했더라도 ToIntBiFunction<T, U>의 사용은 불가했다.
    • 그 이유는 무엇일까? 위 단락의 구조가 같더라도 직접 구현해야 하는 경우를 살펴보자.
  • Comparator의 특성을 정리하면 세 가지다. (이 중 하나 이상을 만족한다면, 전용 함수형 인터페이스 구현을 고려하자)
    • 자주 쓰이며, 이름 자체가 용도를 명확히 설명해준다.
    • 반드시 따라야 하는 규약이 있다.
    • 유용한 디폴트 메서드를 제공할 수 있다.

주의할 점

  • 전용 함수형 인터페이스를 작성하기로 했다면, 자신이 작성하는 게 다른 것도 아닌 인터페이스임을 명심하라.
    • 설계에 아주 큰 주의를 요한다.

@FuntionalInterface 애너테이션

  • 직접 만든 함수형 인터페이스에는 항상 이 애너테이션을 사용하자.
  • @FuntionalInterface 애너테이션의 세 가지 목적
    1. 해당 클래스의 코드나 설명 문서를 읽을 이에게 그 인터페이스가 람다용으로 설계된 것임을 알려준다.
    2. 해당 인터페이스가 추상 메서드를 오직 하나만 가지고 있어야 컴파일 됨을 알려준다.
    3. 유지보수 과정에서 누군가 실수로 메서드를 추가하지 못하게 막아준다.
  • 주의할 점
    • 서로 다른 함수형 인터페이스를 같은 위치의 인수로 받는 메서드들은 다중 정의해서는 안 된다.
      • 모호함만 안겨준다. 실제로 문제가 되기도 한다.(형변환 문제 등)
      • 아이템 52: 다중정의는 주의해서 사용하라

핵심 정리

  • 자바가 람다를 지원하게 되면서, API 설계 시 람다를 염두에 두어야 하는 시대가 왔다.
  • 입력값과 반환값에 함수형 인터페이스 타입을 활용하라.
  • 보통 java.util.function 패키지의 표준 함수형 인터페이스를 사용하는 것이 가장 좋은 선택이다.
  • 흔치는 않지만 앞선 내용처럼 직접 구현하는 것이 좋은 경우도 있다. 이러한 경우 직접 새로운 함수형 인터페이스를 정의해 사용할 수도 있다.
This post is licensed under younghwani by the author.

[Effective Java] Item43. 람다보다는 메서드 참조를 사용하라!

[Effective Java] Item45. 스트림은 주의해서 사용하라!

Comments powered by Disqus.