개발/Effective Java

[Effective Java] item 43. 람다보다는 메서드 참조를 사용하라

nova_dev 2021. 2. 24. 00:00
반응형

[Effective Java] item 43. 람다보다는 메서드 참조를 사용하라

메서드 참조와 람다

람다가 익명 클래스보다 나은 점 중에서 가장 큰 특징은 간결함이다. 그런데 자바에서는 함수 객체를 심지어 람다보다도 더 간결하게 만드는 방법이 있으니, 바로 메서드 참조다.

다음 코드는 임의의 키와 Integer 값의 매핑을 관리하는 프로그램의 일부다. 이때 값이 키의 인스턴스 개수로 해석된다면, 이 프로그램은 멀티셋(multiset)을 구현한게 된다. 이 코드는 키가 맵안에 없다면 키와 숫자 1을 매핑하고, 이미 있다면 기존 매핑 값을 증가시킨다.

map.merge(key, 1, (count, incr) -> count + incr);

이 코드는 자바 8때 Map에 추가된 marge 메서드를 사용했다. marge 메서드 키, 값, 함수를 인수로 받으며, 주어진 키가 맵 안에 아직 없다면 주어진 (키, 값) 쌍을 그대로 저장한다. 반대로 키가 이미 있다면 (세 번째 인수로 받은) 함수를 현재 값과 주어진 값에 적용한 다음, 그 결과로 현재 값을 덮어쓴다. 즉, 맵에 [키, 함수의 결과] 쌍을 저장한다. 이 코드는 merge의 전형적인 쓰임을 잘 보여주고 있다.

깔끔해 보이는 코드이지만 아직도 거추장스러운 부분이 남아있다. 매개변수인 count와 incr은 크게 하는 일 없이 공간을 꽤 차지한다. 사실 이 람다는 두 인수의 합을 단순히 반환할 뿐이다. 자바 8이 되면서 Integer 클래스(와 모든 기본 타입의 박싱 타입)은 이 람다의 기능과 같은 정적 메서드 sum을 제공하기 시작했다. 따라서 람다 대신 이 메서드의 참조를 전달하면 똑같은 결과를 더 보기 좋게 얻을 수 있다.

map.merge(key, 1, Integer::sum)

매개변수 수가 늘어날수록 메서드 참조로 제거할 수 있는 코드 양도 늘어난다. 하지만 어떤 람다에서는 매개변수의 이름 자체가 프로그래머에게 좋은 가이드가 되기도 한다. 이런 람다는 길이가 더 길지만 메서드 참조보다 읽기 쉽고 유지보수도 쉬울 수 있다.

람다로 할 수 없는 일이라면 메서드 참조로도 할 수 없다(애매한 예외가 하나 있는데, 궁금하면 아이템 마지막의 보충설명을 참고하길 바란다). 그렇더라도 메서드 참조를 사용하는 편이 보통은 더 짧고 간결하므로, 람다로 구현했을 때 너무 길거나 복잡하면 메서드 참조가 좋은 대안이 되어준다. 즉, 람다로 작성한 코드를 새로운 메서드에 담은 다음, 람다 대신 그 메서드 참조를 사용하는 식이다. 메서드 참조에는 기능을 잘 드러내는 이름을 지어줄 수 있고 친절한 설명을 문서로 남길 수도 있다.

IDE들은 람다를 메서드 참조로 대체하라고 권할 것이다. IDE의 권고를 따르는게 보통은 이득이지만, 항상 그런 것은 아니다. 때론 람다가 메서드 참조보다 간결할 때가 있다. 주로 메서드와 람다가 같은 클래스에 있을 때 그렇다.

예를들어 다음 코드가 GoshThisClassNameIsHumongous 클래스 안에 있다고 해보자.

service.execute(GoshThisClassNameIsHumongous::action);

이를 람다로 대체하면 다음처럼 된다.

service.execute(() -> action());

메서드 참조 쪽은 더 짧지도, 더 명확하지도 않다. 따라서 람다 쪽이 낫다. 같은 선상에서 java.util.function 패키지가 제공하는 제네릭 정적 팩터리 메서드인 Function.identity()를 사용하기보다는 똑같은 기능의 람다(x-> x)를 직접 사용하는 편이 코드도 짧고 명확하다.

메서드 참조의 유형 다섯 가지

메서드 참조 유형 같은 기능을 하는 람다
정적 Integer::parseInt str -> Integer.parseInt(str)
한정적(인스턴스) Instance.now()::isAfter Instance then = Instant.now();
t -> then.isAfter(t)
비한정적(인스턴스) String::toLowerCase str -> str.toLowerCase()
클래스 생성자 TreeMap<K, V>::new () -> new TreeMap<K, V>()
배열 생성자 int[]::new len -> new int[len]

핵심 정리

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