[교재 EffectiveJava] 아이템 43. 람다보다는 메서드 참조를 사용하라

반응형
728x90
반응형

메서드 참조

자바 8부터 등장한 람다는 익명 클래스보다 간결하다. 자바에는 함수 객체를 람다보다도 더 간결하게 만드는 방법이 있는데, 바로 메서드 참조(method reference) 다.

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

 

이때 값이 키의 인스턴스 개수로 해석된다면 이 프로그램은 멀티셋(multiset)을 구현한게 된다. 

1) key 가 없을 경우 : 1
2) key 가 있을 경우 : 기존 매핑값을 증가

 

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

 

이 코드는 자바 8때 Map 에 추가된 merge 메서드이다. merge 메서드는 키, 값, 함수를 인수로 받으며 주어진 키가 맵 안에 아직 없다면 주어진 {키, 값} 쌍을 그대로 저장하고, 키가 있다면 함수를 현재 값과 주어진 값에 적용한 다음, 그 결과로 현재 값을 덮어씌운다. 즉 맵에 {키, 함수의 결과} 를 저장한다. 

 

다시 위 예제 코드를 보자.
map.merge(key, 1, (count, incr) -> count + incr);

 

매개변수 count, incr가 크게 하는 일 없이 공간을 차지하고있다. 사실 이 람다는 두 인수의 합을 단순히 반환할 뿐이다. Integer 클래스는 이 람다와 기능이 같은 정적 메서드 sum을 제공한다. 이를 람다 대신 이 메서드의 참조를 전달하여 똑같은 결과를 얻어보자.

 

메서드 참조 사용 코드
map.merge(key, 1, Integer::sum);

매개변수의 수가 늘어날수록 메서드 참조로 제거할 수 있는 코드 양도 늘어난다. 메서드 참조를 사용하면 코드가 간결해지지만, 만일 어떤 람다에서 매개변수의 이름 자체가 좋은 가이드가 된다거나, 길이는 좀더 길어도 메서드 참조보다 읽기 쉽고 유지보수가 쉬워질 경우에는 기존의 람다 코드를 사용해도 된다. 

 

람다로 할수 없는 일이라면, 메서드 참조로도 할수 없다. 람다로 작성할 코드를 새로운 메서드에 담은 다음, 람다 대신 메서드 참조를 사용하는 식이다. 메서드 참조에는 기능을 잘 드러내는 이름을 지어줄 수 있고 친절한 설명을 문서로 남길 수도 있다. IDE 들도 람다를 메서드 참조로 대체하라고 권한다. 

 

 

람다의 사용이 더 나은 경우

메서드 참조
service.execute(GoshThisClassNameIsHumongous::action);

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

 

람다 코드로 변경
service.execute(() -> action());

메서드 참조 쪽은 더 짧지도, 명확하지도 않다. 이런 경우에는 람다 코드가 더 낫다. 

 

 

메서드 참조의 유형 

1) 정적 메서드를 기리키는 메서드 참조

위 예제에서 우리는 정적 메서드를 기리키는 메서드 참조를 알아봤다.

/* 메서드 참조 */
Integer::parseInt

/* 람다 */
str -> Integer.parseInt(str)

인스턴스 메서드를 참조하는 유형도 2가지가 있다.

 

2) 수신 객체(receiving object; 참조 대상 인스턴스)를 특정하는 한정적 인스턴스 메서드 참조
/* 메서드 참조 */
Instant.now()::isAfter

/* 람다 */
Instant then = Instant.now()
t -> then.isAfter(t)

 

3) 수신 객체를 특정하지 않는 비한정적 인스턴스 메서드 참조
/* 메서드 참조 */
String::toLowerCase

/* 람다 */
str -> str.toLowerCase()

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

 

비한정적 참조는 주로 스트림 파이프라인에서의 매핑과 필터 함수에 쓰인다. 

 

4) 클래스 생성자를 가리키는 메서드 참조
/* 메서드 참조 */
TreeMap<K, V>::new

/* 람다 */
() -> new TreeMap<K,V>()

 

5) 배열 생성자를 가리키는 메서드 참조
/* 메서드 참조 */
int[]::new

/* 람다 */
len -> new int[len]

 

 

반응형

Designed by JB FACTORY