NMP-Study / EffectiveJava2022

Effective Java Study 2022
5 stars 0 forks source link

아이템 43. 람다보다는 메서드 참조를 사용하라 #43

Closed okhee closed 1 year ago

okhee commented 1 year ago

람다보다는 메서드 참조를 사용하라

요약

  1. 람다보다 메서드 참조가 더 가독성이 높아질 때만 이용하자
  2. 메서드 참조에 인자가 전달되는 방식을 잘 이해하고 이용하자

메서드 참조 (Method Reference)

ClassName::methodName 형식으로 메서드 참조를 생성한다.

메서드 참조 (Method Reference)를 이용하면 기존 메서드 정의를 재활용해, 람다 표현식보다 더 가독성이 좋으며 자연스러울 수 있다.

// As-is
map.merge(key, 1, (count, incr) -> count + incr)

// To-be 
map.merge(key, 1, Integer::sum);

Map.merge()란?

default V merge(K key, V value,
        BiFunction<? super V, ? super V, ? extends V> remappingFunction) {
    // ...
    V oldValue = get(key);
    V newValue = (oldValue == null) ? value :
                remappingFunction.apply(oldValue, value);
    if (newValue == null) {
        remove(key);
    } else {
        put(key, newValue);
    }
    return newValue;
}

Method Reference vs. Lambda Expression

때로는 람다 표현식이 간단할 때가 있다. 코드 재사용성, 클래스 및 메소드명 길이, 메서드 로직의 단순한 정도를 고려하여 가독성이 높은 쪽으로 결정하자.

메서드 참조 유형 & 인자 전달

정적, 한정적, 인스턴스 등은 이해가 어렵다.

ClassName::methodName 기본 형식을 따라간다. 생성자는 methodName으로 new를 이용한다.

메서드 참조는 뒤에 parenthese가 생략되므로 인자 전달 방식이 헷갈릴 수 있다. (사실 제가 ㅎㅎ..) 다음 두 가지만 기억하자.

  1. 메서드 참조에 전달된 모든 인자는 가리키는 메서드의 인자로 그대로 들어간다.
  2. 오직 한 가지 예외가 있는데, 가리키는 메서드가 'Class의 instance method'일 때 & :: 앞에 Class의 instance가 전달되지 않았을 때! 전달된 인자들 중 '첫 번째' 인자가 Class의 instance로 들어간다.
    • instance method가 실행되기 위해선 Class의 instance가 필수이기 때문!
    • instance가 제공되지 않았으므로 인자 목록 중 첫 번째 인자로 이를 전달해야 한다.
// Integer::parseInt
(str) -> {
    return Integer.parseInt(str)
}

// Instant.now()::isAfter
Instant now = Instant.now();
(t) -> {
    return now.isAfter(t);
}

// 인자 전달 방식의 유일한 예외
// String::toLowerCase
(str) -> {
    return str.toLowerCase()
}
// java의 '(str) ->' 형식에서 'str.() ->' 로 바뀜
fun String.toLowerCase(): String.() -> String = // ...
"HeLlO".toLowerCase()
@FunctionalInterface
interface Function5<One, Two, Three, Four, Five, Result> {
    Result apply(One one, Two two, Three three, Four four, Five five);
}

public class Function5Main {
    private static Integer mult5(int i1, int i2, int i3, int i4, int i5) {
        return i1 * i2 * i3 * i4 * i5;
    }
    private static Integer param12345(Function5<Integer, Integer, Integer, Integer, Integer, Integer> function5) {
            return function5.apply(1, 2, 3, 4, 5);
      }

        System.out.println(param12345(Function5Main::mult5));
}

Generic Lambda Expression

그런 건 없다. https://stackoverflow.com/questions/22588518/lambda-expression-and-generic-defined-only-in-method#answer-22588738 하지만 Generic Method Reference는 일부 가능하니 참고하자.

interface G1 {
      <E extends Exception> Object m() throws E;
}

interface G2 {
      <E extends Exception> String m() throws Exception;
}

@FunctionalInterface
interface G extends G1, G2 {}

class GG implements G {
    @Override
    public <E extends Exception> String m() throws E {
        return null;
    }
}

참고(정훈님 블로그)

userList.forEach(user -> System.out.println(user)); 이 코드의 콜스택을 보면 ArrayList.forEach -> Consumer.accept -> System.out.println 이다. 그런데, Consumer.accept 는 사실 필요없고, depth만 깊어지게 한다. 메소드 레퍼런스를 사용하면 이런 부분을 해결할 수 있다. userList.forEach(System.out::println); https://sjh836.tistory.com/173?category=679845

결론

메서드 참조는 람다의 간단명료한 대안이 될 수 있다. 메서드 참조 쪽이 짧고 명확하다면 메서드 참조를 쓰고, 그렇지 않을 때만 람다를 사용하라.