본문 바로가기

독서찰기(讀書札記)/이펙티브 자바

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

[Why]

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

map.merge(key, 1, (count, incr) -> count + incr);
  • 위 코드는 임의의 키와 Integer 값의 매핑을 관리하는 프로그램의 일부다.
    • 이 코드는 자바 8 때 Map에 추가된 merge 메서드를 사용했다.
    • merge 메서드는 키, 값, 함수를 인수로 받으며, 주어진 키가 맵 안에 아직 없다면 주어진 {키, 값} 쌍을 그대로 저장한다.
    • 반대로 키가 이미 있다면 세 번째 인수로 받은 함수를 현재 값과 주어진 값에 적용한 다음, 그 결과로 현재 값을 덮어쓴다.
      즉, 맵에 {키, 함수의 결과} 쌍을 저장한다.
  • 깔끔해 보이는 코드지만 아직도 거추장스러운 부분이 남아 있다.
    • 매개변수인 count와 incr은 크게 하는 일 없이 공간을 꽤 차지한다. 사실 이 람다는 두 인수의 합을 단순히 반환할 뿐이다.
map.merge(key, 1, Integer::sum);
  • 자바 8이 되면서 Integer 클래스(와 모든 기본 타입의 박싱 타입)는 위 람다와 기능이 같은 정적 메서드 sum을 제공하기 시작했다.

 

[When]

  • 메서드 참조를 사용하는 편이 보통은 더 짧고 간결하므로, 람다로 구현했을 때 길거나 복잡하다면 메서드 참조가 좋은 대안이 되어준다.
  • 하지만 항상 그런 것은 아니다. 때론 람다가 메서드 참조보다 간결할 때가 있다.
    • 주로 메서드와 람다가 같은 클래스에 있을 때 그렇다.
service.execute(GoshThisClassNameIsHumongous::action);
service.execute(() -> action());
  • 메서드 참조 쪽은 더 짧지도, 더 명확하지도 않다. 따라서 람다 쪽이 낫다.

 

[How]

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

메서드 참조의 유형은 다섯 가지로, 가장 흔한 유형은 정적 메서드를 가리키는 메서드 참조다.

  • 나머지 유형 네 가지 중 우선, 인스턴스 메서드를 참조하는 유형이 두 가지 있다.
    하나는 수신 객체(receiving object; 참조 대상 인스턴스)를 특정하는 한정적(bound) 인스턴스 메서드 참조이고, 
    다른 하나는 수식 객체를 특정하지 않는 비한정적(unbound) 인스턴스 메서드 참조다.
    • 한정적 참조는 근본적으로 정적 참조와 비슷하다. 즉, 함수 객체가 받는 인수와 참조되는 메서드가 받는 인수가 똑같다.
    • 비한정적 참조에서는 함수 객체를 적용하는 시점에 수신 객체를 알려준다.
      • 이를 위해 수신 객체 전달용 매개변수가 매개변수 목록의 첫 번째로 추가되며, 그 뒤로는 참조되는 메서드 선언에 정의된 매개변수들이 뒤따른다.
      • 비한정적 참조는 주로 스트림 파이프라인에서의 매핑과 필터 함수에 쓰인다(아이템45).
  • 마지막으로, 클래스 생성자를 가리키는 메서드 참조와 배열 생성자를 가리키는 메서드 참조가 있다.
    생성자 참조는 팩터리 객체로 사용된다.