dsc-sookmyung / 2023-01-Effective-Java-Study

이펙티브 자바 공부하는 스터디입니다
2 stars 3 forks source link

Item44. 표준 인터페이스를 사용하라 #44

Open Mingadinga opened 1 year ago

Mingadinga commented 1 year ago

함수형 인터페이스의 활용 예시

자바가 람다를 지원하면서 API를 작성하는 모범 사례가 크게 바뀌었다. 특히 함수 객체를 사용할 수 있게 되어서, 템플릿 패턴 대신 함수 객체를 매개변수로 넘기는 방법이 선호된다.

템플릿 메소드를 사용하는 예시로 LinkedHashMap의 removeEldestEntry가 있다. LinkedHashMap에 키를 새로 추가하는 put 메소드는 내부적으로 afterNodeInsertion 템플릿 메소드를 호출하고, 이는 같은 인스턴스에 위치하는 removeEldestEntry()를 호출한다. removeEldestEntry()는 put에서 아이템을 추가한 후 기존 원소의 제거 유무를 결정하는데 사용된다. removeEldestEntry를 재정의하면 캐시로 활용할 수 있다.


// HashMap
final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evic) {
        // 노드 추가..
        afterNodeInsertion(evict);
}

// LinkedHashMap
void afterNodeInsertion(boolean evict) { // possibly remove eldest
        LinkedHashMap.Entry first;
        if (evict && (first = head) != null && removeEldestEntry(first)) {
                K key = first.key;
                removeNode(hash(key), key, null, false, true);
        }
}

protected boolean removeEldestEntry(Map.Entry eldest) {
        return false;
}

// 캐시로 활용하는 경우 - 최신 아이템 100개 유지
protected boolean removeEldestEntry(Map.Entry eldest) {
        return size() > 100; // size는 Map의 인스턴스 메소드
}

템플릿 메소드 패턴을 사용하는 대신 removeEldestEntry와 동일한 역할을 하는 함수 객체를 정의해서 afterNodeInsertion에 매개변수로 넘겨줄 수 있다. 이때 필요한 함수 객체의 인터페이스를 정의해야하는데, boolean을 반환해야한다. Map의 size 정보가 필요하므로 매개변수로 Map.Entry<K,V>와 Map<K, V>가 필요하다. 다음과 같은 함수형 인터페이스를 선언한다.

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

이 함수는 매개변수를 두개 받아 boolean을 반환한다. 이러한 함수 객체는 자바가 미리 정의해둔 표준 함수형 인터페이스인 BiPredicate를 사용해도 된다. 표준 함수형 인터페이스를 사용하면 사용자 정의 함수 인터페이스를 직접 읽어보지 않아도 되기 때문에 가독성과 이해의 측면에서 더 좋다.

표준 함수형 인터페이스 종류

총 43개의 표준 함수형 인터페이스가 있다. 큰 분류는 다음과 같다.

기본 함수형 인터페이스

인터페이스 함수 시그니처 설명
UnaryOperator T apply(T t) String::toLowerCase T를 인수로 받아 T를 반환
Predicate boolean test(T t) Collection::isEmpty T를 인수로 받아 boolean을 반환
Function<T,R> R apply(T t) Arrays::asList T를 인수로 받아 R을 반환
Supplier T get() Instant::now 인수를 받지 않고 T를 반환
Consumer void accept(T t) System.out::println T를 인수로 받아 값을 반환하지 않음
public class JavaBiConsumer {

    static <T> void addTwo(T a1, T a2, BiConsumer<T, T> c) {
        c.accept(a1, a2);
    }

    public static void main(String[] args) {
        addTwo(1, 2, (x, y) -> System.out.println(x + y));          // 3
        addTwo("Node", ".js", (x, y) -> System.out.println(x + y)); // Node.js
    }

}

기본 타입 변형

기본 타입인 int, long, double로 변형이 생김

Operator

Predicate

Function

Consumer

Supplier

새로운 함수형 인터페이스 정의

새로운 함수형 인터페이스를 정의해야하는 경우

주의사항 : @FunctionalInterface 애너테이션을 사용하자

새로 정의한 함수형 인터페이스 예시 : Comparator<T>

@FunctionalInterface
public interface Comparator<T> {
    int compare(T o1, T o2);
}

Comparator 사용 예시

class Student {
    int rollno;
    // ..
}

// main()
ArrayList<Student> ar = new ArrayList<Student>();
ar.add(new Student(111, "Mayank", "london"));
ar.add(new Student(131, "Anshul", "nyc"));
ar.add(new Student(121, "Solanki", "jaipur"));
ar.add(new Student(101, "Aggarwal", "Hongkong"));

// Collections.sort(List<T> list, Comparator<? super T> c)
Collections.sort(ar, (o1, o2) -> o1.rollno - o2.rollno);