티스토리 뷰

반응형

자바가 람다를 지원하면서 바뀐 것

상위 클래스의 기본 메서드를 재정의해 원하는 동작을 구현하는 템플릿 메서드 패턴

→ 함수 객체를 받는 정적 팩터리나 생성자를 제공하는 것 (함수 객체를 매개변수로 받는 생성자와 메서드를 더 많이 만들어야함

ex) LinkedHashMap 의 removeEldestEntry 메서드 (맵의 가장 오래된 원소 제거)

//재정의함 -> 원소가 100개를 넘어가면 가장 오래된 원소를 제거 
protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
    return size() > 100;
}

위 코드를 람다를 사용하여 함수형 인터페이스를 만들 경우에는 아래와 같다.

//불필요한 함수형 인터페이스 - 대신 표준 함수형 인터페이스를 사용하라
@FuntionalInterface 
interface EldestEntryRemovalFunction<K, V> {
    boolean remove(Map<K, V> map, Map.Entry<K, V> eldest);
}

위 인터페이스도 잘 작동하긴 하나, 자바 표준 라이브러리(java.util.function)에 비슷한 표준 함수형 인터페이스가 있으니 그걸 사용하자.

위에 직접 만든 EldestEntryRemovalFunction 메서드 대신 표준 인터페이스인 BiPredicate<Map<K, V>, Map.Entry<K, V>> 를 사용할 수 있다.

 

 

java.util.function 패키지의 기본 인터페이스 6개

이 패키지에는 총 43개의 인터페이스가 있지만, 기본 인터페이스 6개만 기억하면 나머지는 유추할 수 있다.

인터페이스 함수 시그니처 설명
UnaryOperator T apply(T t) String::toLowerCase 반환값과 인수의 타입이 같은 함수(인수가 1개)
BinaryOperator T apply(T t1, T t2) BigInteger::add 반환값과 인수의 타입이 같은 함수(인수가 2개)
Predicate boolean test(T t) Collection::isEmpty 인수 하나를 받아 boolean을 반환하는 함수
Function<T, R> R apply(T t) Arrays::asList 인수와 반환 타입이 다른 함수
Supplier T get() Instant::now 인수를 받지 않고 값을 반환/제공하는 함수
Consumer void accept(T t) System.out::println 인수를 하나 받고 반환값은 없는(특히 인수를 소비하는) 함수

기본 인터페이스는 기본 타입인 int,long,double 용으로 각 3개씩 변형이 있음(ex.IntPredicate)

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

 

 

기본 함수형 인터페이스의 변형

1) UnaryOperator

  • 기본타입용 : DoubleUnaryOperator, IntUnaryOperator, LongUnaryOperator

2) BinaryOperator

  • 기본타입용 : DoubleBinaryOperator, IntBinaryOperator, LongBinaryOperator

3) Predicate

  • 기본타입용 : DoublePredicate, IntPredicate, LongPredicate
  • 인수를 2개받고 boolean 반환 : BiPredicate<T, U>

4) Function<T, R>

  • 유일하게 Function의 변형만 매개변수화됨 → ex.LongFunction<int[]>은 long 인수를 받아 int[]를 반환
  • 입력은 기본타입, 출력은 R타입 : DoubleFunction, IntFunction, LongFunction
  • 입력과 출력 모두 기본타입(..To..Function) : LongToIntFunction, DoubleToLongFunction 등등
  • 출력이 기본타입 : ToDoubleFunction, ToIntFunction, ToLongFunction
  • 인수를 2개받고 R타입 반환 : BiFunction<T,U,R>
  • 인수를 2개받고 기본타입 반환 : ToDoubleBiFunction<T,U>, ToIntBiFunction<T,U>, ToLongBiFunction<T,U>

5) Supplier

  • 기본타입용 : DoubleSupplier, IntSupplier, LongSupplier, BooleanSupplier

6) Consumer

  • 기본타입용 : DoubleConsumer, IntConsumer, LongConsumer
  • 인수 2개 받음 : BiConsumer<T, U>
  • T타입, 기본타입 받음 : ObjDoubleConsumer, ObjIntConsumer, ObjLongConsumer

 

 

직접 함수형 인터페이스를 작성해야할 때

표준 함수형 인터페이스가 있더라도 용도에 맞는게 없다면 직접 작성해야할 때가 있다.

예를 들어, 자주 본 Comparator 인터페이스는 구조적으로는 ToIntBiFunction<T,U> 인터페이스와 같지만 직접 작성한 예이다.

다음 세가지 조건 중에 하나 이상을 만족한다면, 직접 작성해야하는 것이 아닌지 고민해보자.

  • 자주 쓰이며, 이름 자체가 용도를 명확히 설명해준다.
    • ex) Comparator 이름이 ToIntBiFunction<T,U>보다 용도를 명확히 설명한다.
  • 반드시 따라야 하는 규약이 있다.
    • ex) compare() 는 따라야하는 규약이 많다.
  • 유용한 디폴트 메서드를 제공할 수 있다.
    • ex) Comparator 는 reversed(), thenComparing() 등등의 메서드를 제공한다

 

@FunctionalInterface

직접 작성할때 인터페이스 위에 @FunctionalInterface 애너테이션을 꼭 붙여주자.

이는 @Override를 붙여주는 것과 같은 이유로 다음과 같은 목적이 있다.

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

 

 

함수형 인터페이스를 API에서 사용할 때 주의사항

public interface ExecutorService extends Executor {
  // 생략

  <T> Future<T> submit(Callable<T> task);
  Future<?> submit(Runnable task);

  // 생략
}

서로 다른 함수형 인터페이스를 같은 위치의 인수로 받는 메서드들을 다중 정의해서는 안된다. → 모호함

올바르메서드를 알려주기 위해 형변환 해야할때가 생긴다.

이런 상황은 피하자.

 

 

결론

자바도 이제 람다를 지원하기 때문에 API를 설계할 때 람다를 염두에 두고 설계하자.

입력값과 반환값에 함수형 인터페이스 타입을 활용하라.

대게 표준 함수형 인터페이스를 쓰도록 하고, 직접 새로운 함수형 인터페이스를 만들어 쓰는 편이 나을 때도 있다.

반응형
댓글
반응형
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday