[Effective Java 3/E] ITEM 44. 표준 함수형 인터페이스를 사용하라

    Effective Java 표지. Source : https://blog.insightbook.co.kr/

    *알림 : *
    Effective Java 3판은 Java 9까지 도입된 언어적 기능을 중심으로 서술되어 있습니다. 10버젼 이후의 Java 개발을 하시는 분들은 우회적인 접근법 대신 Java 언어 내 새로 도입된 기능이 더 간결하고 좋을 수 있습니다.

    해당 포스팅은 SSAFY 내 책읽기 스터디의 활동을 통해 작성된 포스팅입니다.
    https://github.com/kjsu0209/JavaBook
    https://medium.com/javabook

    람다를 도입하면서 API 작성 모범사례ㄴ도 많이 바뀌었다. 상위 클래스의 기본 메서드를 재정의 하는 템플릿 메서드 패턴의 동기가 떨어지고 대신 함수 객체를 매개변수로 받는 생성자와 메서드로 동작을 바꾸는 것을 권장한다. 그러나 함수 객체를 전달할 때 매개변수 타입을 신경써야 한다.

    LinkedHashMap 예시로 살펴보기

    removeEldestEntry() : https://javafactory.tistory.com/735

    removeEldestEntry()*를 재정의하면 특정 조건 하에서 가장 오래된 원소를 제거할 수 있다. 이 메서드는 *put() 메서드 내에서 호출되며, 기본적으로는 return false; 즉, 오래된 원소를 제거하지 않는다. 아래와 같이 재정의를 한다면, 가장 최근 원소 100개만을 유지한다.

    예제 1 : LinkedHashMap의 removeEldestEntry() 재정의

    protected boolean removeEldestEntry(Map.Entry<K,V> eldest){
            return size() > 100;
    }

    생성자에 함수 객체를 넘기는 방법으로 구현했다면 조금 더 좋았을뻔 했다는 생각이 든다.

    함수형 인터페이스를 사용한다면 어떻게 될까? 람다의 size()는 해당 Map의 인스턴스가 아니기 때문에 Map 자기자신도 함수 객체에 건내줘야 위와 같은 동작을 하도록 보장할 수 있을 것이다.

    @FunctionalInterface interface EldestEntryRemovalFunction<K,V>{
            boolean remove(Map<K,V> map, Map.Entry<K,V> eldest);
    }

    해당 인터페이스를 구현할 때, 특정 조건 하에서 eldest를 지우도록 true값을 반환하면 작동할 것이다.

    근데 이렇게 직접 구현하는 대신, 라이브러리에 존재하는 동일한 폼의 인터페이스를 사용하자. java.util.function에 정의되어 있으며, 아래 표를 참조하면 좋다.

    표준 함수형 인터페이스를 사용하여야 하는 동기

    1. API가 다루는 개념의 수가 줄어들어 API User가 학습하기 쉬워진다.
    2. 표준 함수형 인터페이스는 이미 똑똑한 사람들이 설계해 구현한 것이다.
    3. 유용한 디폴트 메서드를 많이 제공하고, 표준 함수형 인터페이스를 사용하는 다른 코드와 상호 운용성도 좋아질 것이다.

    Untitled

    이 외에도 43개의 많은 인터페이스가 있지만, 이 기본형 인터페이스의 개념을 기반으로 하기 때문에 이 6개만 잘 알아놓는다면 나머지는 이름만 보고도 유추할 수 있다.

    표준 함수형 인터페이스 변형

    • 표준형 앞에 int long, double로 3개씩 변형이 있다. ex) int+Predicate = IntPredicate
    • Function은 좀 특이한데, Function<T,R> + Long → LongFunction (리턴타입이 R이고 long을 받는다.)
    • UnaryOperator, 인수와 같은 타입을 반환하는 인터페이스가 이미 있기 때문에 Function 인터페이스는 입력과 결과 타입이 같지 않다. 만약 입력과 결과 모두 기본타입이면 SrcToResult를 앞에 붙인다. ex) ㅣLongToIntFunction
    • 입력을 접미사로 표현하지 않고 매개변수화 한다면, 결과값의 타입이 기본타입일 경우 ToResult를 사용한다. ex)ToLongFunction<int[]>
    • 인수를 2개씩 받고 싶다면, Bi 접두사를 붙인다. ex) BiPredicate<T,U>, BiFunction<T,U,R>. ToResult를 또 붙일 수도 있다.

    주의할 점

    1. 기본 타입을 지원하는 함수형 인터페이스에 박싱된 타입을 쓰지말자.
    2. 느리다.
    3. 표준형 인터페이스가 존재하더라도 새로 함수형 인터페이슬 작성해야 할 때가 있다.사용할 때 따라야 하는 규약이 있을 때
    4. 유용한 디폴트 메서드를 제공해야 할 필요가 있을 때.
    5. 이름 자체가 용도를 명확히 설명해줄 때
    6. Overloading은 하지 말자.
    7. 사용하는 클라이언트가 혼란을 느낄 수 있다.

    @FunctionalInterface

    1. 해당 인터페이스는 람다용으로 설계된 것임을 표기하는 Marker의 기능
    2. 해당 인터페이스가 추상 메서드를 하나만 가질 때 컴파일 될 수 있도록 한다.

    댓글