JAVA/Effective Java

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

꾸준함. 2024. 3. 2. 12:51

메서드 참조

메서드 참조는 특정 메서드를 참조하여 매개변수와 리턴 타입을 알아낸 뒤, 람다식의 불필요한 부분을 제거하여 코드를 간결하게 만드는 기법입니다.

이를 통해 가독성을 향상하고 람다 표현식보다 더 간결한 코드를 작성할 수 있습니다.

 

람다식이 단 하나의 메서드를 호출한 경우에 해당 람다식에서 불필요한 매개변수를 없앨 수 있으며 이때 형식은 `클래스명::메서드명`의 형태로 작성해 간결한 메서드 참조로 변경할 수 있습니다.

 

메서드 참조 예시

 

Map 인터페이스의 merge 메서드를 예제 코드를 작성하겠습니다.

 

Map 인터페이스의 merge 메서드

 

merge 메서드 설명

  • 전달받은 key가 map 내 존재하지 않는다면 value 값을 (key, value) 쌍으로 저장
  • 전달받은 key가 map 내 존재한다면 value 값을 BiFunction의 apply 메서드에 적용해 {key, BiFunction.apply() 메서드의 결과} 쌍으로 저장
  • BiFunction 인터페이스는 하나의 추상 메서드만 가지고 있는 함수형 인터페이스이기에 람다를 이용해 구현 가능

 

1. 람다 표현식을 사용하는 경우


Map<String, Integer> frequencyTable = new TreeMap<>();
for (String s : args) {
frequencyTable.merge(s, 1, (count, incr) -> count + incr); // Lambda
}
System.out.println(frequencyTable);
view raw .java hosted with ❤ by GitHub

 

2. 메서드 참조를 사용하는 경우

 

Map<String, Integer> frequencyTable = new TreeMap<>();
for (String s : args) {
frequencyTable.merge(s, 1, Integer::sum); // Method reference
}
System.out.println(frequencyTable);
view raw .java hosted with ❤ by GitHub

 

이처럼 하나의 메서드를 호출하는 람다 표현식을 메서드 참조로 변경할 경우 보다 간결해지는 것을 볼 수 있습니다.

 

하나의 메서드를 호출하더라도 람다 표현식을 사용해야 하는 경우

메서드 참조를 사용하면 코드가 간결해지지만, 람다의 매개변수명이 개발자들에게 더 나은 가이드가 되는 경우가 있어서 코드 길이가 길어지더라도 람다 표현식을 사용하는 경우가 있습니다.

예를 들어 다음과 같이 호출하고자 하는 메서드가 엄청 긴 이름을 가진 클래스로부터 호출될 경우 가독성 측면에서 람다를 사용하는 것이 더 좋은 코드라고 할 수 있습니다.

 

// 메서드 참조
service.execute(GoshThisClassNameIsHumongous::action);
// 람다 사용
service.execute(() -> action());
view raw .java hosted with ❤ by GitHub

 

코드 부연 설명

  • 위 코드를 작성했을 때 Intellij와 같은 IDE들은 람다를 메서드 참조로 대체하라고 권할 것이고 대부분의 케이스에서는 IDE의 추천을 이행하는 것이 권장되지만
    • 위 코드의 경우 클래스명이 너무 길기 때문에 메서드 참조로 변환하더라도 코드가 짧아지지도 않을뿐더러 명확하지도 않음
    • 람다 쪽이 더 나음

 

메서드 참조 유형

메서드 참조 유형은 총 다섯 가지입니다.

  • 정적 메서드
  • 한정적 인스턴스 메서드
  • 비한정적 인스턴스 메서드
  • 클래스 생성자
  • 배열 생성자

 

1. 정적 메서드

 

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

 

2. 한정적 인스턴스 메서드

 

  • 정적 메서드 참조와 비슷하게 수신 객체를 특정
  • 수신 객체는 람다의 파라미터 중 하나로 제공되는 특정 객체
  • 즉, 특정 객체의 메서드를 호출하는 메서드 참조

 

3. 비한정적 인스턴스 메서드

 

  • 수신 객체를 특정하지 않고 적용하는 시점에 수신 객체를 알려줌
  • 이미 존재하는 외부 객체를 호출하는 상황
  • 비한정적 참조는 주로 스트림 파이프라인에서의 매핑과 필터 함수에 쓰임
  • 비한정적 인스턴스 메서드 참조는 특정 객체에 제한되지 않기 때문에 최상위 계층인 Object의 메서드 참조

 

4. 클래스 생성자

 

  • 클래스 생성자를 가리키는 메서드 참조
  • 팩터리 객체로 사용

 

5. 배열 생성자

 

  • 배열의 생성자를 가리키는 메서드 참조


public class Example {
public static void main(String[] args) {
// 정적 메서드
BinaryOperator<Integer> methodReference = Integer::sum;
BinaryOperator<Integer> lambdaExpression = (a, b) -> Integer.sum(a, b);
// 한정적 인스턴스 메서드
Instant then = Instant.now();
Predicate<Instant> boundedMethodReference = Instant.now()::isAfter;
Predicate<Instant> boundedLambdaExpression = t -> then.isAfter(t);
// 비한정적 인스턴스 메서드
Function<String, Integer> unboundedMethodReference = String::length;
Function<String, Integer> unboundedLambdaExpression = s -> s.length();
// 클래스 생성자
Supplier<List<String>> constructorReference = ArrayList::new;
Supplier<List<String>> constructorLambdaExpression = () -> new ArrayList<>();
// 배열 생성자
Function<Integer, int[]> arrayConstructorReference = int[]::new;
Function<Integer, int[]> arrayConstructorLambdaExpression = size -> new int[size];
}
}
view raw .java hosted with ❤ by GitHub

 

비고

람다 표현식으로 표현할 수 없으면 일반적으로 메서드 참조로도 작성할 수 없습니다.

그러나 한 가지 예외가 있는데 람다로는 제네릭 함수 타입 구현을 표현할 수 없지만 메서드 참조를 활용하여 구현이 가능합니다.

제네릭 람다식이라는 문법이 존재하지 않기 때문입니다.

 

interface G1 {
<E extends Exception> Object m() throws Exception;
}
public class Example {
public static void main(String[] args) {
// G1 인터페이스를 메서드 참조로 구현
G1 g1 = Example::func;
}
public static Object func() throws RuntimeException {
return new Object();
}
}
view raw .java hosted with ❤ by GitHub

 

위 코드는 메서드 참조를 사용해 제네릭 함수 타입 구현을 표현해서 컴파일 에러가 발생하지 않지만 이를 람다로 변경할 경우 컴파일 에러가 발생하는 것을 볼 수 있습니다.

 

 

참고

이펙티브 자바

반응형