본문 바로가기

Reading/Effective Java

[Effective-Java] Item 43. 람다보다는 메소드 참조를 사용하라

메소드 참조(method reference)


: 람다식이 단 하나의 메소드만을 호출하는 경우 필요없는 매개변수를 제거하고 사용할 수 있게 하는 것

 

람다로도 익명클래스보다 간결한데 한 층 더 간격하게 만드는 방법이다. 아래 코드를 보자

 

Map<String, Integer> frequencyTable = new TreeMap<>();
String[] argg = {"k","t","a","e","k","t"};
for (String s : argg)
    frequencyTable.merge(s, 1, (count, incr) -> count + incr); // 람다
for (String s : argg)
    frequencyTable.merge(s, 1, Integer::sum); // 메서드 참조

 

매개변수 count와 incr은 두 인수의 합을 단순히 반환할 뿐인데 거창하다.

Integer 클래스와 모든 기본 타입의 박싱 타입은 이 람다와 기능이 같은 정적 메소드 sum을 제공한다.

그래서 이 메소드의 참조를 전달하면 똑같은 결과를 더 간결하게 보일 수 있다.

 

여기서 사용되는 Map 인터페이스의 merge 메소드는 다음과 같다.

 

 

 

키, 값, 함수를 인수로 받으며 주어진 키가 맵 안에 아직 없다면 주어진 {키,값} 쌍을 그대로 저장하고,

키가 존재한다면 세 번째 인수의 함수를 현재 값과 주어진 값에 적용한 다음 그 결과로 현재 값을 덮어쓴다.

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

 

 

람다를 쓰는게 더 나은 경우


1. 매개변수의 이름 자체가 메소드 참조보다 읽기 쉬운 경우

길이는 더 길지만 더 직관적이다. 하지만 보통은 메소드 참조가 더 낫다.

 

2. 메소드와 람다가 같은 클래스에 있는 경우이거나 람다가 메소드보다 단순하고 직관적일 경우

 

service.execute(GoshThisClassNameIsHumongous::action); // 메소드 참조
service.execute(()->action()); // 람다

list.stream().map(Function.identity()); // x->x와 같은 기능
list.stream().map(x->x); // 람다

 

따라서 메소드 참조 쪽이 짧고 명확하다면 메소드 참조를 쓰고, 그렇지 않을 때만 람다를 쓰자.

 

 

메소드 참조의 다섯 유형


메소드 참조 유형  같은 기능을 하는 람다
정적 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]

 

한정적(bound) 인스턴스: 참조 대상 인스턴스를 특정하는 인스턴스

함수 객체가 받는 인수와 참조되는 메소드가 받는 인수가 똑같다. 참조 대상 인스턴스 전달용 매개변수가 매개변수 목록의 첫 번째로 추가되고 그 뒤로 참조되는 매소드 선언에 정의된 매개변수들이 뒤따른다. 다음 두 예시를 참고하자

 

// (1)
List<Integer> integers = List.of(1, 2, 3);
// 람다
// Predicate<Integer> listTwoIndexEq = obj -> integers.get(2).equals(obj);
Predicate<Integer> listTwoIndexEq = integers.get(2)::equals;
System.out.println(listTwoIndexEq.test(3)); // true

// (2)
// 람다
//Predicate<Instant> parameterIsAfter = otherInstant -> Instant.now().isAfter(otherInstant);
Predicate<Instant> parameterIsAfter = Instant.now()::isAfter;
System.out.println(parameterIsAfter.test(Instant.MIN)); //true

 

비한정적(unbound) 인스턴스: 참조 대상 인스턴스를 특정하지 않는 인스턴스

함수 객체를 적용하는 시점에 참조 대상 인스턴스를 알려주며 주로 스트림 파이프라인에서 매핑과 필터 함수에 쓰인다.

 

 

참고용


람다로는 불가능하나 메소드 참조로는 가능한 유일한 예는 바로 제네릭 함수 타입(generic function type) 구현이다.

 

interface G1{
    <E extends RuntimeException> Object m() throws E;
}
interface G2 {
    <F extends RuntimeException> String m() throws RuntimeException;
}

@FunctionalInterface
interface G extends G1, G2{

}
public class GenericFunctionType {

    private static <F extends Throwable> String m() throws RuntimeException {
        return "kTae";
    }

    private static void gClass(G g){
        try {
            System.out.println(g.m());
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        try {
            //gClass(()->m()); // 함수형 인터페이스의 제네릭 메소드는 람다식이 안된다는 것.
            gClass(GenericFunctionType::m); // 메소드 참조 표현식은 가능
        }catch (Exception e){
            e.printStackTrace();
        }
        G g  = new G() {
            @Override
            public <F extends RuntimeException> String m() throws RuntimeException {
                return null;
            }
        };
        try {
            gClass(g); // 만들어진 객체 가능
            //gClass(()->g.m()); // 람다 불가능
            gClass(g::m); // 메소드 참조 표현식 가능
            //throw new Exception();
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

 

 

 

 

* 위 글은 EffectiveJava 3/E 책 내용을 정리한 글로, 저작권 관련 문제가 있다면 댓글로 남겨주시면 즉각 삭제조치 하겠습니다.