-
Notifications
You must be signed in to change notification settings - Fork 2
Open
Labels
🐜 07 Lamdbas & Streams7장 람다와 스트림7장 람다와 스트림
Description
Chapter : 7. 람다와 스트림
Item : 44. 표준 함수형 인터페이스를 사용하라
Assignee : youngkimi
🍑 서론
함수형 인터페이스를 새로 구현하기 보다는 표준 함수형 인터페이스를 활용하라.
- 자바가 람다를 지원하며 API를 작성하는 모범 사례도 바뀌었다.
- 기존처럼 상위 클래스의 기본 메서드를 재정의하는
템플릿 메서드 패턴보다는, 함수 객체를 받는정적 팩터리나생성자를 제공하는 것이 보다 현대적이다. - 일반화하여 말하면, 함수 객체를 매개변수로 받는 생성자와 메서드를 더 만들어야 한다.
🍑 본론
LinkedHashMap
- 이 클래스의
protected메서드인removeEldestEntry를 재정의하면 캐시로 사용할 수 있다. Map에 새로운 키를 추가하는put()메서드는 이 메서드를 호출하여 true가 반환되면 맵에서 가장 오래된 원소를 제거한다.
// 상위 클래스의 removeEldestEntry를 재정의.
// Entry Size가 100을 넘으면, true를 반환할 것이다.
protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
return size() > 100;
}- 이는 잘 동작하지만, 람다를 사용하면 더 좋을 것이다.
LinkedHashMap을 오늘날 다시 구현한다면, 함수 객체를 받는 정적 팩터리나 생성자를 제공했을 것이다.
표준 함수형 인터페이스를 사용해라
removeEldestEntry선언을 보면, 이 함수 객체는 Entry인Map.Entry<K, V> eldest을 받아boolean을 반환할 것 같은데, 꼭 그렇지는 않다.- 이 메서드는
size()를 호출해 맵 안의 원소 수를 알아내는데,removeEldestEntry가인스턴스 메서드기에 가능한 방식이다. - 하지만 생성자에 넘기는 함수 객체는 이 맵의
인스턴스 메서드가 아니다. 팩터리나 생성자를 호출할 때는 맵의 인스턴스가 존재하지 않기 때문이다. 맵은 자기 자신도 함수 객체에 건네줘야 한다.
@FunctionalInterface interface EldestEntryRemovalFunction<K, V> {
boolean remove(Map<K, V> map, Map.Entry<K, V> eldest);
}- 이 인터페이스도 잘 작동하지만, 자바 표준 라이브러리에 이미 같은 라이브러리가 준비되어 있다.
- 표준 함수형 인터페이스를 사용하라. 유용한 디폴트 메서드를 제공하고, 다른 코드와의 상호 운용성도 좋아진다.
Predicate인터페이스는 predicate를 조합하는 메서드를 제공한다.- 앞의 예에서 직접 만든
EldestEntryRemovalFunction대신 표준 인터페이스인BiPredicate<Map<K, V>, Map.Entry<K, V>>를 사용할 수 있다.
자주 사용하는 기본 인터페이스 6가지
java.util.functional에는 43개의 인터페이스가 담겨 있다.- 전부 기억하기는 어렵지만, 기본 인터페이스 6개만 기억하면 나머지는 유추 가능하다.
Operator(Unary, Binary)
- 반환값과 인수의 타입이 같은 함수를 의미한다.
- 인수가 한 개인
UnaryOperator,BinaryOperator로 나뉜다.
Predicate
- 앞서 말한 것처럼, 인수 하나를 받아
boolean을 반환하는 함수를 말한다.
Function
- 인수와 반환 타입이 다른 함수를 뜻한다.
Supplier
- 인수는 받지 않고, 값을 반환하는 함수를 뜻한다.
Consumer
- 인수는 받고, 반환값은 없는(특히 인수를 소비하는) 함수를 뜻한다.
기본 인터페이스 변형
다 외우기에는 수도 많고 규칙성도 부족하다.
-
기본 인터페이스는 Primitive Type인
int,long,double용으로 각 3개씩 변형이 생긴다. -
기본 인터페이스 앖에 해당 기본 타입
-
Function인터페이스의 변형은 입력과 반환값의 타입이 항상 다르므로 기본 타입을 반환하는 변형이 총 9개 (3*3) 더 있다. (기본 변형의 접두사는입력타입을 나타냄)- 6개 : 인수와 같은 타입을 반환하는 함수는
UnaryOperator이므로, Function 인터페이스의 변형은 입력, 결과 타입이 항상 다르다.SrcToRes접두사가 붙은Function인터페이스는 6가지가 있다.LongToIntFunction - 3개 : 입력이 객체 참조이고 결과가
int,long,double인 변형들. 앞서와는 달리 입력을 매개변수화하고 접두어로ToResult를 사용한다.ToLongFunction<int[]>
- 6개 : 인수와 같은 타입을 반환하는 함수는
-
인수를 두 개씩 받는 인터페이스 변형 :
BiPredicate<T, U>,BiFunction<T, U, R>,BiConsumer<T, U> -
기본 타입(
int,long,double)으로 반환하는 BiFunction의 변형 :ToIntBiFunction<T, U> -
객체 참조와 기본 타입(
int,long,double) 하나 받는 변형 :ObjIntConsumer<T> -
boolean을 반환하는 Supplier :BooleanSupplier -
표준 함수형 인터페이스에 박싱된 기본 타입을 넣어 사용하지는 말자. 동작은 하지만, 계산량이 많으면 성능이 느려질 수 있다. (item 61)
함수형 인터페이스를 직접 구현해야 하는 경우
아래 조건 중 하나 이상을 만족한다면 고민해보아야 한다.
- 자주 사용하며, 이름이 그 용도를 훌륭히 설명해주는 경우
- 구현하는 쪽에서 반드시 지켜야할 규약을 담고 있는 경우
- 유용한 디폴트 메서드를 가지고 있는 경우
Comparator<T>를 떠올려보자.
- 구조적으로는
ToIntBiPredicate<T, U>와 동일하다. Comparator<T>를 자바 라이브러리에 추가할 당시ToIntBiPredicate<T, U>가 있었음에도 사용하지 않았다. (않아야 했다.)
직접 만든 함수형 인터페이스에는 항상 @FunctionalInterface 를 사용하라
@Override를 사용하는 이유와 비슷하다.
- 해당 클래스의 코드를 읽는 사람에게 해당 인터페이스가 람다용으로 설계된 것임을 알려준다.
- 해당 인터페이스가 추상 메서드를 오직 하나 가지고 있어야 컴파일되게 해준다.
- 그 결과 누군가 메서드를 추가 못하게 막아준다.
함수형 인터페이스를 API에서 사용할 때의 주의점
- 서로 다른 함수형 인터페이스를 같은 위치의 인수로 받는 메서드들을
다중 정의하지 말라.- 클라이언트에게 모호함을 안겨준다. 모호함으로 실제로 문제가 발생하기도 한다.
ExecutorService의submit()메서드는Callable<T>,Runnable<T>를 받는 경우를다중 정의한다.- 이로 인해 올바른 메서드를 알려주기 위해 형변환 해야 하는 경우가 생긴다 (item 52)
🍑 결론
자바가 람다를 지원함을 생각하고 API를 설계하라.
- 입력값과 반환값에 함수형 인터페이스 타입을 활용하라.
- 일반적으로는 표준 함수형 인터페이스를 사용하라.
- 간혹 가다 직접 사용해야 하는 경우가 있을 수 있다. 위 조건을 참고하라.
Referenced by
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
🐜 07 Lamdbas & Streams7장 람다와 스트림7장 람다와 스트림