Effective Java : 아이템 43. 람다보다는 메서드 참조를 사용하라.

     

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

    • 람다가 메서드 참조보다 가독성이 좋은 경우를 제외하면, 메서드 참조를 사용하라. 람다가 가독성이 더 좋으면 람다를 사용하라.
    • 람다를 메서드 참조로 바꿔서 사용하는 경우 다음 장점 존재
      • 람다는 이름이 없으나 메서드는 이름이 가짐. → 가독성 증가 가능.
      • 람다는 문서화 불가능 → 메서드 참조의 메서드는 문서화 가능 
      • 람다에 이름을 붙여서 메서드를 만들면 됨.
    • 메서드 참조는 5가지 타입이 존재함.
      • 정적 메서드 참조
      • 한정적 메서드 참조(인스턴스)
      • 비한정적 메서드 참조(인스턴스)
      • 클래스 생성자 메서드 참조
      • 배열 생성자 메서드 참조 

     


    람다보다 메서드 참조를 사용해라

    익명 클래스보다 람다는 간결해서 많이 쓰인다. 람다보다 더 간결한 것은 메서드 참조다. 따라서 간결함을 위해서 람다보다 메서드 참조를 사용하는 것이 좋다

    public static void main(String[] args) {
    
    
        final HashMap<String, Integer> map = new HashMap<>();
        final String key = "hello";
    
        // 람다식
        map.merge(key, 1, (count, incr) -> count + incr);
    
        // 메서드 참조
        map.merge(key, 1, Integer::sum);
    }

    람다식과 메서드 참조는 위와 같은 차이가 있다. 중요한 차이는 매개변수다. 

    매개변수 count, incr가 없더라도 함수의 의미를 잘 알 수 있다면 매개변수를 표시하는 것보다는 매개변수를 제거해서 가독성을 올리는 것이 좋다. 지금은 2개 정도라서 문제 없지만, 매개변수가 100개인 경우를 상상한다면 메서드 참조가 더욱 좋은 것은 쉽게 알 수 있다. 

    map.merge(key, 1, (num1, num2, num3, num4, num5) -> num1 + num2 + num3 + num4 + num5);
    map.merge(key, 1, Integer::sum);

    위가 바로 그 예시다. 람다식의 의미없는 매개변수를 나열하는 것보다 메서드 참조로 식을 줄이는 것이 더 가독성 있는 문장이 된다. 

     


    람다를 쓰면 더 좋을 때는?

    다음 두 가지 경우에는 람다를 쓰는 것이 더 좋을 수 있다. 

    • 람다식의 매개변수의 이름이 메서드 참조보다 더 좋은 가독성을 제공할 때
    • 메서드 참조를 사용해서 식이 더 길어지는 경우 

    예를 들어 아래 경우를 살펴보자. 아래 경우는 람다로 action()을 표현하는 것이 메서드 참조를 이용하는 것보다 훨씬 코드가 간결하고 읽기 편하다. 람다 / 메서드 참조 중 선택의 기준이 되는 가장 중요한 것은 '가독성'이라고 이해하면 좋을 것 같다. 

    public class GoshThisClassNameIsHumongous {
    
        public void hello() {
            // 람다식이 메서드 참조보다 더 간편함. 
            hello1(() -> action());
            hello1(GoshThisClassNameIsHumongous::action);
        }
    
        public void hello1(Runnable runnable) {
    
        }
        
        public static void action() {
            System.out.println("true = " + true);
        }
    }

     

     

    람다를 메서드 참조로 바꾸는 방법과 장점은?

    람다는 함수의 이름이 없고, 문서화를 할 수 없다는 단점이 있다고 했다. 반면 람다 식을 메서드 참조로 바꾸게 되면 함수의 이름이 생기고 문서화까지 가능하다는 장점이 있다. 일반적으로는 이미 Integer.sum() 같은 것들을 메서드 레퍼런스로 이용하면 되는데 구현이 되어 있지 않은 경우가 있다. 이럴 때는 아래처럼 직접 구현해서 사용하면 된다.

    public class MethodReferenceTestClass {
    
    
        public static void main(String[] args) {
    
            final HashMap<String, Integer> map = new HashMap<>();
            final String key = "hello";
    
            // 람다식
            map.merge(key, 1, (n1, n2) -> (n1 + n2) * n2 * n1);
    
            // 메서드 참조
            map.merge(key, 1, MethodReferenceTestClass::customCalculate);
        }
    
        public static Integer customCalculate(Integer n1, Integer n2) {
            return (n1 + n2) * n2 * n1;
        }
    }

     

     

    메서드 참조 종류

    메서드 참조 유형 같은 기능을 하는 람다
    정적 Integer::parseInt str -> Integer.parseInt(str)
    한정적 (인스턴스) String myString = "hello";
    myString::toLowerCase
    str -> str.toLowerCase();
    비한정적 (인스턴스) String::toLowerCase str -> str.toLowerCase()
    클래스 생성자 TreeMap<K,V>::new () -> new TreeMap<K,V>()
    배열 생성자 int[]::new len -> new int[len]

    정적 메서드 참조는 클래스가 가지고 있는 메서드를 그대로 참조해서 사용하는 것을 의미한다. 예를 들면 아래 경우이다. Math 클래스는 정적 메서드 sqrt를 가지고 있는데 이 메서드를 참조한다. 

    // 정적 메서드 참조
    Function<Double, Double> sqrt = Math::sqrt;

    한정적 참조는 '특정 인스턴스'가 가지고 있는 메서드를 참조한다. 인스턴스 메서드는 public, private 메서드를 의미한다. 한정적 참조는 '특정 인스턴스'가 가지고 있는 메서드를 참조하는 방식이다. 예를 들면 다음과 같이 사용된다.

    String str = "hello";
    Supplier<String> supplier = str::toUpperCase;

    'str'이라는 인스턴스가 가지고 있는 toUpperCase라는 메서드를 참조했다. 특정 인스턴스에 '바인딩'되어서 참조했기 때문에 한정적 참조라고 한다. 

    비한정적 참조는 클래스의 인스턴스가 가지고 있는 public, private 메서드를 참조하지만 '특정 인스턴스의 메서드를 참조하는 것'은 아니다.이 때 정적 메서드를 참조하는 것이 아니라 클래스 인스턴스의 메서드를 참조한다. 아래 Stream()에서 사용되는 것이 비한정적 참조를 의미하는 경우다. 

    map()에는 MethodReferenceTestClass 라는 타입이 전달되는 것을 알고는 있으나, '어떤 객체'인지는 한정되어 있지 않다. 그래서 그 클래스가 가지고 있는 메서드를 참조하는 형태로 사용된다. 이런 것을 비한정적 참조라고 한다. 

    public class MethodReferenceTestClass {
    
    
        public static void main(String[] args) {
            MethodReferenceTestClass r1 = new MethodReferenceTestClass();
            MethodReferenceTestClass r2 = new MethodReferenceTestClass();
            MethodReferenceTestClass r3 = new MethodReferenceTestClass();
            List<MethodReferenceTestClass> myList = List.of(r1, r2, r3);
    
            myList.stream()
                    .map(MethodReferenceTestClass::getName) // 비한정적 참조
                    .collect(Collectors.toList());
        }
    
        public String getName() {
            return this.toString();
        }
    }

     

     

     

    참고

    댓글

    Designed by JB FACTORY