티스토리 뷰

(Effective Java) 규칙43. 람다보다는 메서드 참조를 사용하라


람다


  • 람다가 익명 클래스보다 나은 점 중에서 가장 큰 특징은 간결함인데 람다보다도 더 간결하게 만들 수 있는 방법이 있다.

    • 바로 메서드 참조(method reference)
  • 예) 임의의 키와 Integer 값의 매핑을 관리하는 프로그램

    • 값이 키의 인스턴스 개수로 해석된다면, 이 프로그램은 멀티셋(multiset)을 구현한게 됨
    • 키가 맵 안에 없다면 키와 숫자1을 매핑하고, 이미 있다면 기존 매핑 값을 증가시킴

       

      map.merge(key, 1, (count, incr) -> count + incr);
      • 자바 8때 Map에 추가된 merge 메서드, 키, 값, 함수를 인수로 받아 수행하는 함수임

 

메서드 참조


  • 위 코드는 깔끔한 코드지만 countincr은 크게 할 일 없이 공간을 꽤 차지 함

    • 람다는 두 인수의 합을 단순히 반환할 뿐임
    • 자바 8이 되면서 Integer 클래스(와 모든 기본 타입의 박싱 타입)는 이 람다와 기능이 같은 정적 메서드 sum을 제공하기 시작함
  • 위 코드에 람다 대신에 sum 메서드 참조를 전달하면 똑같은 결과를 더 보기 좋게 얻을 수 있다.

    map.merge(key, 1, Integer::sum);
    • 매개변수가 늘어날수록 메서드 참조로 제거할 수 있는 코드양도 늘어남
  • 단, 매개변수 자체가 의미가 있는 경우는 람다가 더 읽기 쉽고 유지보수도 쉬울 수 있음

 

람다, 메서드 참조의 사용


  • 람다로 할 수 없는 일이라면 메서드 참조로도 할 수 없다. (애매한 예외가 하나 있는데, 마지막에 보충 설명 참고)

  • 메서드 참조를 사용하는 편이 보통은 더 짧고 간결하므로, 람다로 구현했을 때 너무 길거나 복잡하다면 메서드 참조가 좋은 대안이 되어준다.

    • 즉, 람다로 작성할 코드를 새로운 메서드에 담은 다음, 람다 대신 그 메서드참조를 사용하는 식
    • 메서드 참조에 기능을 잘 드러내는 이름을 지어줄 수도 있고 설명을 문서로 남길 수도 있음
  • 다만, 항상 메서드 참조가 적절한 것은 아니다.

    • 예) 참조할 메서드가 GoshThisClassNameIsHumongous 클래스 안에 있다고 할 때
      //메서드 참조를 할 때
      service.execute(GoshThisClassNameIsHumongous::action);
      //람다 사용을 할 때
      service.execute(() -> acation());
    • 예) java.util.function 패키지의 제네릭 정적 팩터리 메서드인 Function.identity()를 사용하기보다는 똑같은 기능의 람다 (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]

유형 분류

  • 정적 메서드를 가리키는 참조

    • 앞에서 본 유형으로 가장 흔한 유형
  • 인스턴스 메서드를 참조하는 유형

    • 수신 객체(receiving object; 참조 대상 인스턴스)를 특정하는 한정적(bound) 인스턴스 메서드 참조
      • 정적 참조와 비슷하며 함수 객체가 받는 인수와 참조되는 메서드가 받는 인수가 똑같음
    • 수신 객체를 특정하지 않는 비한정적(unbound) 인스턴스 메서드 참조
      • 함수 객체를 적용하는 시점에 수신 객체를 알려줌
      • 수신 객체 전달용 매개변수가 매개변수 목록의 첫번째로 추가되며, 그 뒤로는 참조되는 메서드 선언에 정의된 매개변수들이 뒤따름
      • 주로 스트림 파이프라인에서의 매핑과 필터 함수에 쓰임
  • 생성자를 가리키는 참조 (주로 팩터리 객체로 사용됨)

    • 클래스 생성자를 가리키는 참조
    • 배열 생성자를 가리키는 참조

 

결론


  • 메서드 참조는 람다의 간단명료한 대안이 될 수 있다.

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

     

보충 설명


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

자바 명세의 예제 9.9-2(http://bit.ly/2uYQnbh)를 번역한 글

  • 함수형 인터페이스의 추상 메서드가 제네릭일 수 있듯이 함수 타입도 제네릭일 수 있다. 다음의 인터페이스 계층구조를 생각해보자.
    interface G1 {
        <E extends Exception> Object m() throws E;
    }
    interface G2 {
        <F extends Exception> String m() throws Exception;
    }
    interfacea G extends G1, G2 {}
    
  • 이때 함수형 인터페이스 G를 함수 타입으로 표현하면 다음과 같다.
    <F extends Exception> () -> String throws F
  • 이처럼 함수형 인터페이스를 위한 제네릭 함수 타입은 메서드 참조 표현식으로는 구현할 수 있지만, 람다식으로는 불가능하다.
  • 제네릭 람다식이라는 문법이 존재하지 않기 때문이다.

끝으로

이 글이 도움이 되었다면, 하단의 Google 광고 👎👎👎 한번씩 클릭 부탁 드립니다. 🙏🙏🙏

광고 클릭은 많은 힘이 됩니다! 

반응형
댓글