티스토리 뷰

반응형

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

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

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

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