[Effective Java] item 44 : 표준 함수형 인터페이스를 사용하라

DEINGVELOP·2023년 3월 5일
1

Effective Java

목록 보기
15/19

Java 8 이전과 이후

  • Java가 람다를 지원하면서, API 작성 모범 사례도 크게 바뀜

    • 이전 해법 : 템플릿 메서드 패턴이 매력적이었음(상위 클래스의 기본 메서드를 재정의해, 원하는 동작을 구현하는 것)
    • 현대적인 해법 : 같은 효과의 함수 객체를 받는 정적 팩터리나 생성자를 제공하는 것. 즉, 함수 객체를 매개변수로 받는 생성자와 메서드를 더 많이 만들어야 한다는 것.

    정적 팩터리 메서드
    : 객체 생성의 역할을 하는 클래스 메서드

    • 직접적으로 생성자를 통해 객체를 생성하는 것이 아닌, 메서드를 통해서 객체를 생성하는 것
    • 네이밍 컨벤션
      • from : 하나의 매개 변수를 받아서 객체를 생성
      • of : 여러개의 매개 변수를 받아서 객체를 생성
      • getInstance | instance : 인스턴스를 생성. 이전에 반환했던 것과 같을 수 있음.
      • newInstance | create : 새로운 인스턴스를 생성
      • get[OtherType] : 다른 타입의 인스턴스를 생성. 이전에 반환했던 것과 같을 수 있음.
      • new[OtherType] : 다른 타입의 새로운 인스턴스를 생성.

    메서드 참조 - Java의 :: (더블 콜론)
    : 메서드 레퍼런스라고도 불림. 자바 8버전에서 소개됨. 람다식과 똑같은 기능을 함

    • 람다식과의 차이 : 인스턴스를 활용하여 메서드를 전달힘
    • 사용처
      • 사용하는 람다식이 기존에 있는 인스턴스의 메서드와 동일하면, 메서드 러퍼런스로 대체 가능
      • 정적 메서드, 인스턴스 메서드의 메서드 레퍼런스로 사용 가능
    strs.forEach(str -> System.out.println(str));
    strs.forEach(System.out::println);

함수 객체를 매개변수로 받는 생성자와 메서드

  • 이때, 함수형 매개변수 타입을 올바르게 선택해야 한다!

LinkedHashMap

  • removeEldestEntry를 재정의하면 캐시로 사용 가능!

    protcted boolean removeEldestEntry(Map.Entry<K, V> eldest) {
    	return size() > 100;
    }
    • 위와 같이 재정의하면, 맵에 원소가 100개가 될 때까지 커지다가 그 이상이 되면 새로운 키가 더해질 때마다 가장 오래된 원소 하나씩 제거함. 즉, 가장 최근 원소 100개를 유지함
  • 단, 위 메서드는 인스턴스 메서드!

  • 만약 LinkedHashMap이 함수객체를 받는 정적 팩터리나 생성자를 제공하여 구현했다면, 이야기가 달라짐. 생성자에 넘기는 함수 객체가 이 맵의 인스턴스 메서드가 아니기 때문에, 맵은 자기 자신도 함수 객체에 건네줘야 하고.... 이를 반영하면 다음과 같은 함수형 인터페이스로 선언되게 된다.

  • EldestEntryRemovalFunction 함수형 인터페이스 (사실 불필요)

    @FunctionalInterface interface EldestEntryRemovalFunction<K, V> {
    	boolean remove(Map<K, V> map, Map.Entry<K, V> eldest);
    }
    • 이 인터페이스도 잘 동작하지만, 굳이 사용할 필요가 없다. 자바 표준 라이브러리에 이미 같은 모양의 인터페이스가 준비되어 있기 때문

java.util.function 패키지

  • 다양한 용도의 표준 함수형 인터페이스가 담겨 있다.
  • 총 43개의 인터페이스 담겨 있음
    • 기본 6개만 기억하면 나머지는 충분히 유추 가능
    • 모두 참조 타입용임

사용시 장점

  • API가 다루는 개념의 수가 줄어들어 익히기 더 쉬워짐
  • 표준 함수형 인터페이스에 유용한 디폴트 메서드를 많이 제공 -> 다른 코드와의 상호운용성도 크게 좋아짐

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

  • Predicate 인터페이스
    • predicate들을 조합하는 메서드를 제공함
    • BiPredicate<Map<K, V>, Map.Entry<K, V>> : 앞의 EldestEntryRemovalFunction 대신 사용 가능!

기본 함수형 인터페이스 6개

  1. UnaryOperator<T>
    - 인수가 1개인 연산 인터페이스
    - T apply(T t)
    - String::toLowerCase

  2. BinaryOperator<T>
    - 인수가 2개인 연산 인터페이스
    - T apply(T t1, T t2)
    - BigInteger::add

  3. Predicate<T>
    - 인수가 하나를 받아 boolean을 반환하는 함수
    - boolean apply(T t)
    - Collection::isEmpty

  4. Function<T, R>
    - 인수와 반환 타입이 다른 함수
    - R apply(T t)
    - Arrays::asList

  5. Supplier<T>
    - 인수를 받지 않고 값을 반환(혹은 제공)하는 함수
    - T get()
    - Instant::now

  6. Consumer<T>
    - 인수를 하나 받고 반환값은 없는(특히 인수를 소비하는) 함수
    - void accept(T t)
    - System.out::println

  • 기본 인터페이스 : int, long, double 용으로 각 3개씩 변형이 생겨남

    • IntPredicate : int를 받는 predicate
    • LongBinaryPredicate : long을 받아 long 반환하는 predicate
  • 유일하게 매개변수화된 변형 : Function

    • LongFunction<int[]> : long 인수를 받아 int[]를 반환함
    • Function 인터페이스의 기본 타입 반환 변형 : 총 9개

      인수와 같은 타입을 반환하는 함수 : UnaryOperator
      입력과 결과의 타입이 항상 다른 함수 : Function

    • srcToResult : 입력과 결과 타입이 모두 기본 타입이면 사용하는 접두어
      • ex) 총 6개 : LongToIntFunction : long으르 받아 int를 반환
      • ex) 그 외 총 3개 : ToLongFunction<int[]> - int[] 인수를 받아 long을 반환함(총 3개)
  • 기본 함수형 인터페이스 중 3개 BiPredicate<T, U>, BiFunction<T, U, R>, BiConsumer<T, U>에는 인수를 2개씩 받는 변형이 있음
    • BiFunction
      • ToIntBiFunction<T, U>
      • ToLongBiFunction<T, U>
      • ToLongBiFunction<T, U>
  • Consumer
    • ObjDoubleConsumer<T>
    • ObjIntConsumer<T>
    • ObjLongConsumer<T>
  • BooleanSupplier : boolean을 반환하도록 한 Supplier의 변형
    (하지만 Predicate와 그 변형 4개도 boolean 값 반환 가능)

이렇게 총 43개!

  • 솔직히 다 외우기에는 수도 많고 규칙성도 부족함.
  • 하지만 실무에서 자주 쓰이는 함수형 인터페이스 중 상당수를 제공함
  • 필요할 때 찾아 쓸 수 있을 만큼 범용적인 이름 사용함

유의사항

  • 표준 함수형 인터페이스 대부분은 기본 타입만 지원. 그렇다고 해서 기본 함수형 인터페이스에 박싱된 기본 타입을 넣어 사용하지는 말자!

  • 단, 직접 함수형 인터페이스를 만들어야 함!

0개의 댓글