DEV ℧ Developer Diary

[EffectiveJava] item43 - 람다보다는 메서드 참조를 사용하라

람다가 익명 클래스보다 나은 점 중에서 가장 큰 특징은 간결함이다.

하지만 메서드 참조(method reference)를 사용하면 함수 객체를 람다보다 더 간결하게 만들어 줄 수 있다.

간단하게 예시를 들어보자.

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

키가 맵안에 없다면 키와 숫자 1을 매핑하고, 이미 있다면 기존 매핑 값을 증가시키는 코드이다.

merge 메서드는 자바 8때 Map에 추가된 메서드 이다.
키, 값, 함수를 인수로 받고 주어진 키가 맵 안에 없다면 주어진 {키, 값}을 그대로 저장한다. 반대로 키가 있마녀 함수를 현재 값과 주어진 값에 적용한 다음, 그 결과로 현재 값을 덮어쓴다.

깔끔해 보이는 코드지만 매개변수인 count와 incr은 크게 하는 일 없이 공간을 차지한다. 해당 코드중 람다 대신 같은 기능인 Interger 클래스의 정적 메서드 sum을 이용하면 똑같은 결과를 더 보기 좋게 얻을 수 있다.

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

매개변수 수가 늘어날수록 메서드 참조로 제거할 수 있는 코드양도 늘어난다.

람다와 메소드 참조

람다로 할 수 없는 일이라면 메서드 참조로도 할 수 없다. 그렇지만 메서드 참조를 사용하는 편이 더 짧고 간결하므로, 람다로 구현했을 때 너무 길어진다면 메서드 참조가 좋은 대안이 되어준다.
또한 메서드 참조에는 기능을 잘 드러내는 이름을 지어줄 수 있고 문서화 하여 남길 수 있다.

하지만 모든 코드를 메서드 참조 유형으로 변환하는 것은 옳지 않다.

하지만 때론 람다가 메서드 참조보다 간결할 때가 있다. 주로 람다와 메서드가 같은 클래스에 있을때이다.

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

service.execute(GoshThisClassNameIsHumongous::action);

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

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

메서드 참조 쪽은 더 짧지도, 더 명확하지도 않으니, 람다 쪽이 낫다.

같은 예시로 java.util.function 패키지가 제공하는 제네릭 정적 팩터리 메서드인 Function.identity()도 똑같은 기능인 람다(x -> x)를 사용하는 편이 코드도 짧고 명확하다.

메서드 참조 유형

정적 메서드 참조 유형

방금 예시로 소개한 유형은 정적 메서드 유형 참조로, 가장 흔한 유형이다. 메서드 참조의 유형은 총 다섯가지가 있다. 이제 나머지 유형들을 소개해 보고자 한다.

인스턴스 참조 유형

인스턴스 메서드를 참조하는 유형은 두 가지 있다. 그중 하나는 수신 객체(receiving object; 참조 대상 인스턴스)를 특정하는 한정적(bound) 인스턴스 메서드 참조이고, 다른 하나는 수신객체를 특정하지 않는 비한정적(unbound) 인스턴스 메서드 참조다.

한정적 참조는 근본적으로 정적 참조와 비슷하다. 함수와 객체가 받는 인수와 참조되는 메서드가 받는 인수가 똑같다.

비한정적 참조에서는 함수 객체를 적용하는 시점에 수신 객체를 알려준다. 이로 인해 수신 객체 전달용 매개변수가 매개변수 목록의 첫번째로 추가되며, 그뒤로는 참조되는 메서드에 정의된 매개변수들이 뒤따른다.

클래스 생성자, 배열 생성자

마지막으로, 클래스 생성자를 가리키는 메서드 참조와 배열 생성자를 가리키는 메서드 참조가 있다. 생성자 참조는 팩터리 객체로 사용된다.

정리

다음의 표를 통해 다섯가지를 간략하게 정리해볼 수 있다.

메서드 참조 유형 예시 같은 기능을 하는 람다
정적 Integer::parseInt str -> Integer.parseInt(str)
한정적 (인스턴스) Instant.now()::isAfter Instant 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]