표준 함수형 인터페이스를 사용하라
자바가 람다를 지원하면서 API를 작성하는 모범 사례도 변화
템플릿 메서드 패턴 → 함수 객체를 받는 정적 팩터리나 생성자를 제공
protected 메서드인 remvoeEldestEntry를 재정의 하면 캐시로 사용 가능
맵에 새로운 키를 추가하는 put 메서드는 이 메서드를 호출하여 true가 반환되면 가장 오래된 원소를 제거
재정의
removeEldestEntry를 아래와 같이 재정의하면 맵에 새로운 키가 더해질 때마다 가장 오래된 원소를 하나씩 제거
즉 가장 최근 원소 100개를 유지
protected boolean removeEldestEntry (Map.Entry<K, V> eldest) {
return size() > 100;
}
removeEldestEntry는 size를 호출하는데, removeEldestEntry가 인스턴스 메서드라 가능한 방식
- 생성자에 넘기는 함수 객체는 이 맵의 인스턴스 메서드가 아니기 때문
- 팩터리나 생성자를 호출할 때는 맵의 인스턴스가 존재하지 X
- 따라서 map은 자기 자신도 함수 객체도 넘겨주어야 함
@FuntionalInterface
inferface EldestEntryRemovalFunction<K, V> {
boolean remove(Map<K, V> map, Map.Entry<K, V> eldest);
}
필요한 용도에 맞는 게 있다면 직접 구현말고 표준 함수형 인터페이스를 활용해라.
java.util.function 패키지를 보면 다양한 용도의 표준 함수형 인터페이스가 담겨 있다.
인터페이스 예시
Operator 인터페이스
인수가 1개인 UnaryOperator와 2개인 BinaryOperator로 나뉘며 반환값과 인수의 타입이 같은 함수를 뜻함
// 인터페이스
UnaryOperator<T>
BinaryOperator<T>
// 함수 시그니처
T apply(T t)
T apply(T t1, T t2)
// 예
String::toLowerCase
BigInteger::add
Predicate 인터페이스
인수 하나를 받아 boolean을 반환하는 함수
// 인터페이스
Predicate<T>
// 함수 시그니처
boolean test(T t)
// 예
Collection::isEmpty
Function 인터페이스
인수와 반환 타입이 다른 함수
// 인터페이스
Function<T>
// 함수 시그니처
R test(T t)
// 예
Arrays::asList
Supplier 인터페이스
인수를 받지 않고 값을 반환 또는 제공하는 함수
// 인터페이스
Supplier<T>
// 함수 시그니처
T get()
// 예
Instant::now
Consumer 인터페이스
인수를 하나 받고 값을 반환값은 없는 함수를 뜻함
// 인터페이스
Consumer<T>
// 함수 시그니처
void accept(T t)
// 예
System.out::println
주의점
표준 함수형 인터페이스 대부분은 기본 타입만 지원한다
기본 함수형 인터페이스에 박싱된 기본 타입을 넣어 사용하지 말자
동작은 하지만, 박싱된 기본 타입 대신 기본 타입을 사용하자는 것에 위배되며
성능이 처참히 느려질 수 있다.
Comparator가 독자적인 인터페이스인 이유
구조적으로는 ToIntBiFunction<T, U>와 동일
심지어 자바 라이브러리에 Comparator<T>를 추가할 당시 ToIntBiFunction가 있었어도 사용하지 X
왜?
- API에서 많이 사용되는데 이름이 용도를 잘 설명해줌
- 구현하는 쪽에서 반드시 지켜야 할 규약을 담고 있음
- 비교자들을 변환하고 조합해주는 유용한 디폴트 메서드들을 담고 있음
따라서 이런 경우엔 전용 함수형 인터페이스의 구현를 고민하자
- 자주 쓰이며 이름 자체가 용도를 명확하게 설명
- 반드시 따라야 하는 규약 존재
- 유용한 디폴트 메서드 제공 가능
@FunctionalInterface 애너테이션
@Override를 사용하는 이유와 비슷한데, 프로그래머의 의도를 명시하는 것
목적
- 해당 클래스의 코드나 설명 문서를 읽을 이에게 그 인터페이스가 람다용으로 설계된 것임을 알려줌
- 해당 인터페이스가 추상 메서드를 오직 하나만 가지고 있어야 컴파일되게 해줌
- 그 결과 유지보수 과정에서 누군가 실수로 메서드를 추가하지 못하게 해줌
그러니 직접 만든 함수형 인터페이스에는 항상 @FunctionalInterface 어노테이션을 사용하라
API에서 사용 시
서로 다른 함수형 인터페이스를 같은 위치의 인수로 받는 메서드들을 다중 정의해선 안됨
클라이언트에게 불필요한 모호함을 안겨줄 뿐만 아니라, 문제가 일어나기도 함
이런 문제를 피하는 가장 쉬운 방법은 서로 다른 함수형 인터페이스를 같은 위치의 인수로 사용하는 다중정의를 피하는 것이다.
'독서📚' 카테고리의 다른 글
자바 모듈 시스템(+ module-info.java & setting.gradle) (1) | 2024.02.07 |
---|---|
[아이템 43] 람다보다는 메서드 참조를 사용하라 (0) | 2023.09.15 |
[아이템 42] 익명 클래스보다는 람다를 사용하라 (0) | 2023.09.15 |
새로운 인프라 환경이 온다 (0) | 2023.09.12 |
[자바의 정석] 1. 자바를 시작하기 전에 (0) | 2023.06.27 |
표준 함수형 인터페이스를 사용하라
자바가 람다를 지원하면서 API를 작성하는 모범 사례도 변화
템플릿 메서드 패턴 → 함수 객체를 받는 정적 팩터리나 생성자를 제공
protected 메서드인 remvoeEldestEntry를 재정의 하면 캐시로 사용 가능
맵에 새로운 키를 추가하는 put 메서드는 이 메서드를 호출하여 true가 반환되면 가장 오래된 원소를 제거
재정의
removeEldestEntry를 아래와 같이 재정의하면 맵에 새로운 키가 더해질 때마다 가장 오래된 원소를 하나씩 제거
즉 가장 최근 원소 100개를 유지
protected boolean removeEldestEntry (Map.Entry<K, V> eldest) {
return size() > 100;
}
removeEldestEntry는 size를 호출하는데, removeEldestEntry가 인스턴스 메서드라 가능한 방식
- 생성자에 넘기는 함수 객체는 이 맵의 인스턴스 메서드가 아니기 때문
- 팩터리나 생성자를 호출할 때는 맵의 인스턴스가 존재하지 X
- 따라서 map은 자기 자신도 함수 객체도 넘겨주어야 함
@FuntionalInterface
inferface EldestEntryRemovalFunction<K, V> {
boolean remove(Map<K, V> map, Map.Entry<K, V> eldest);
}
필요한 용도에 맞는 게 있다면 직접 구현말고 표준 함수형 인터페이스를 활용해라.
java.util.function 패키지를 보면 다양한 용도의 표준 함수형 인터페이스가 담겨 있다.
인터페이스 예시
Operator 인터페이스
인수가 1개인 UnaryOperator와 2개인 BinaryOperator로 나뉘며 반환값과 인수의 타입이 같은 함수를 뜻함
// 인터페이스
UnaryOperator<T>
BinaryOperator<T>
// 함수 시그니처
T apply(T t)
T apply(T t1, T t2)
// 예
String::toLowerCase
BigInteger::add
Predicate 인터페이스
인수 하나를 받아 boolean을 반환하는 함수
// 인터페이스
Predicate<T>
// 함수 시그니처
boolean test(T t)
// 예
Collection::isEmpty
Function 인터페이스
인수와 반환 타입이 다른 함수
// 인터페이스
Function<T>
// 함수 시그니처
R test(T t)
// 예
Arrays::asList
Supplier 인터페이스
인수를 받지 않고 값을 반환 또는 제공하는 함수
// 인터페이스
Supplier<T>
// 함수 시그니처
T get()
// 예
Instant::now
Consumer 인터페이스
인수를 하나 받고 값을 반환값은 없는 함수를 뜻함
// 인터페이스
Consumer<T>
// 함수 시그니처
void accept(T t)
// 예
System.out::println
주의점
표준 함수형 인터페이스 대부분은 기본 타입만 지원한다
기본 함수형 인터페이스에 박싱된 기본 타입을 넣어 사용하지 말자
동작은 하지만, 박싱된 기본 타입 대신 기본 타입을 사용하자는 것에 위배되며
성능이 처참히 느려질 수 있다.
Comparator가 독자적인 인터페이스인 이유
구조적으로는 ToIntBiFunction<T, U>와 동일
심지어 자바 라이브러리에 Comparator<T>를 추가할 당시 ToIntBiFunction가 있었어도 사용하지 X
왜?
- API에서 많이 사용되는데 이름이 용도를 잘 설명해줌
- 구현하는 쪽에서 반드시 지켜야 할 규약을 담고 있음
- 비교자들을 변환하고 조합해주는 유용한 디폴트 메서드들을 담고 있음
따라서 이런 경우엔 전용 함수형 인터페이스의 구현를 고민하자
- 자주 쓰이며 이름 자체가 용도를 명확하게 설명
- 반드시 따라야 하는 규약 존재
- 유용한 디폴트 메서드 제공 가능
@FunctionalInterface 애너테이션
@Override를 사용하는 이유와 비슷한데, 프로그래머의 의도를 명시하는 것
목적
- 해당 클래스의 코드나 설명 문서를 읽을 이에게 그 인터페이스가 람다용으로 설계된 것임을 알려줌
- 해당 인터페이스가 추상 메서드를 오직 하나만 가지고 있어야 컴파일되게 해줌
- 그 결과 유지보수 과정에서 누군가 실수로 메서드를 추가하지 못하게 해줌
그러니 직접 만든 함수형 인터페이스에는 항상 @FunctionalInterface 어노테이션을 사용하라
API에서 사용 시
서로 다른 함수형 인터페이스를 같은 위치의 인수로 받는 메서드들을 다중 정의해선 안됨
클라이언트에게 불필요한 모호함을 안겨줄 뿐만 아니라, 문제가 일어나기도 함
이런 문제를 피하는 가장 쉬운 방법은 서로 다른 함수형 인터페이스를 같은 위치의 인수로 사용하는 다중정의를 피하는 것이다.
'독서📚' 카테고리의 다른 글
자바 모듈 시스템(+ module-info.java & setting.gradle) (1) | 2024.02.07 |
---|---|
[아이템 43] 람다보다는 메서드 참조를 사용하라 (0) | 2023.09.15 |
[아이템 42] 익명 클래스보다는 람다를 사용하라 (0) | 2023.09.15 |
새로운 인프라 환경이 온다 (0) | 2023.09.12 |
[자바의 정석] 1. 자바를 시작하기 전에 (0) | 2023.06.27 |