-
item 44 표준 함수형 인터페이스를 사용하라책/이펙티브 자바 2022. 4. 11. 16:17
ITEM 44 표준 함수형 인터페이스를 사용하라
자바가 람다를 지원하며 API를 작성하는 모범 사례도 크게 바뀌었다
한 예로 템플릿 메서드 패턴의 매력이 크게 줄었다
- 상위 클래스의 기본 메서드를 재정의해 원하는 동작을 구현하는 패턴
이를 대체하는 현대적인 해법은 아래와 같다
- 같은 효과의 함수 객체를 받는 정적 팩터리나 생성자를 제공하는 것
- 이 내용을 일반화해서 말하면 함수 객체를 매개변수로 받는 생성자와 메서드를 더 많이 만들어야한다
- 이때 함수형 매개변수 타입을 올바르게 선언해야 한다
LinkedHashMap을 예로 들어보자 LinkedHashMap의 protected 메서드인 removeEldestEntry를 재정의하면 캐시로 사용 가능하다 맵에 새로운 키를 추가하는 put 메서드는 이 메서드를 호출하여 true가 반환되면 맵에서 가장 오래된 원소를 제거한다 removeEldestEntry를 다음처럼 재정의하면 맵에 원소가 100개가 될 때까지 커지다가 그 이상이 되면 새로운 키가 더해질 때마다 가장 오래된 원소를 하나씩 제거한다 (가장 최근 원소 100개를 유지하는것) protected boolean removeEldestEntry(Map.Entry<K,V> eldest) { return size() > 100; } 위 코드는 잘 동작하지만 람다를 사용하면 더 깔끔하다 만약 LinkedHashMap을 요즘 문법으로 다시 구현한다면 함수 객체를 받는 정적 팩터리나 생성자를 제공했을것이다 removeEldestEntry 선언을 보면 이 함수 객체는 Map.Entry<K,V>를 받아 boolean을 반환해야 할 것 같지만 그건 아니다 removeEldestEntry는 size()를 호출해 맵 안의 원소 수를 알아내는데, removeEldestEntry가 인스턴스 메서드라서 가능한 방식이다 하지만 생성자에 넘기는 함수 객체는 이 맵의 인스턴스 메서드가 아니다 팩터리나 생성자를 호출할 때는 맵의 인스턴스가 존재하지 않기 때문이다 따라서 맵은 자기 자신도 함수 객체에 건네줘야 한다 아래의 코드는 위 내용을 반영한 함수형 인터페이스이다 불필요한 함수형 인터페이스 - 대신 표준형 함수 인터페이스르 사용하자 @FunctionalInterface interface EldestEntryRemovalFunction<K,V> { boolean remove(Map<K,V> map, Map.Entry<K,V> eldest); } 위 인터페이스도 잘 동작하지만 자바 표준 라이브러리에 이미 같은 모양의 인터페이스가 있어서 굳이 사용할 이유는 없다 java.util.function 패키지에 여러가지 용도의 표준 함수형 인터페이스가 존재하니 필요한 용도에 맞는게 있으면 직접 구현하지 말고 표준 함수형 인터페이스를 사용하자
표준 함수형 인터페이스 대부분은 기본 타입만 지원하는데 기본 함수형 인터페이스에 박싱된 기본 타입을 넣어서 사용하지말자
- 동작은 하지만 후에 나올 "박싱된 기본 타입 대신 기본 타입을 사용하라" item 61을 위반하게 되며 계산량이 많은 경우 성능이 처참하게 느려진다
위에서 살펴 보았듯 대부분의 경우에는 직접 작성하는 것 보다 표준 함수형 인터페이스를 사용하는것이 더 효율적이다
그럼 코드를 작성해야 하는 경우는 없을까?
- 표준 인터페이스 중 필요한 용도에 맞는게 없으면 직접 작성해야 한다
- 구조적으로 같은 표준 함수형 인터페이스라도 아래의 조건에 해당되는게 있다면 따로 구현하는게 좋다
- 자주 쓰이며 이름 자체가 용도를 명확히 설명해준다
- 반드시 따라야 하는 규약이 있다
- 유용한 디폴트 메서드를 제공할 수 있다
- 구조적으로 같지만 따로 구현된게 Comparator
인터페이스이다(ToIntBiFunction<T,U>와 같은 구조) - 전용 함수형 인터페이스를 설계 할 때에는 인터페이스인 만큼 아주 주의해서 설계 해야된다
- 직접 만든 함수형 인터페이스에는 항상 @FunctionalInterface 애너테이션을 사용하자
- 이 어노테이션은 @Overried와 비슷한 이유로 사용한다 (프로그래머의 의도를 명시 하는것)
- 아래와 같은 세가지 목적이 있다
- 해당 클래스의 코드나 설명 문서를 읽을 이에게 그 인터페이스가 람다용으로 설계된걸 알려준다
- 해당 인터페이스가 추상 메서드를 오직 하나만 가지고 있어야 컴파일 되게 해준다
- 그 결과 유지보수 과정에서 누군가 실수로 메서드를 추가하지 못하게 막아준다
함수형 인터페이스를 API에서 사용하는 경우 주의점
- 서로 다른 함수형 인터페이스를 같은 위치의 인수로 받는 메서드들을 다중 정의해서는 안된다
- 클라이언트에게 불필요한 모호함만 안겨주며 이 모호함으로 인해 실제로 문제가 발생할 수 있다
- ExecutorService의 submit 메서드는 Callable
를 받는 것과 Runnable을 받는 것을 다중정의해서 올바른 메서드를 알려주기 위해 형변환 해야하는 경우가 종종 생긴다 - 이런 문제를 피하려면 서로 다른 함수형 인터페이스를 같은 위치의 인수로 사용하는 다중정의를 하지 않는게 가장 간단하다
- item 52 다중정의는 주의해서 사용하라의 특수한 예
이제 자바도 람다를 지원하는 만큼 API 설계시 람다도 고려 해야한다
입력값과 반환값에 함수형 인터페이스 타입을 활용하자
대부분 java.util.function 패키지에 있는 표준 함수형 인터페이스를 사용하는게 가장 좋다
하지만 가끔 직접 새로운 함수형 인터페이스를 만드는게 나은 경우도 있으니 그 사실을 잊지말자
'책 > 이펙티브 자바' 카테고리의 다른 글
item 46 스트림에서는 부작용 없는 함수를 사용하라 (0) 2022.04.12 item 45 스트림은 주의해서 사용하라 (0) 2022.04.11 item 43 람다보다는 메서드 참조를 사용하라 (0) 2022.04.11 item 42 익명 클래스보다는 람다를 사용하라 (0) 2022.04.06 item 41 정의하려는 것이 타입이라면 마커 인터페이스를 사용하라 (0) 2022.04.06