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

2023. 5. 13. 15:51Java/Effective Java

메서드 참조(Method Reference)

  • 함수를 메서드의 파라미터로 전달하는 것을 메서드 참조
  • class::methodName 구문을 사용하여 클래스 또는 객체에서 메서드를 참조
  • 람다식(Lambda Expression)의 가장 큰 장점 중 하나는 코드가 짧아진다는 것인데, 람다식에 메서드 참조를 사용하면 코드를 더 간결하고 가독성 있게 만들 수 있다.

 

※ 자바8 Map merge 메서드

  • 키, 값, 함수를 인수를 받으며, 주어진 키가 맵 안에 없다면 주어진 [키, 값] 쌍을 그대로 저장
  • 주어진 키가 맵 안에 있다면 함수(세번째 인수로 받은)를 현재 값과 주어진 값에 적용한 다음, 그결과로 현재 값을 덮어쓴다. 즉, [키, 함수의 결과] 쌍을 저장한다.
Map<String, Integer> keyCount = new HashMap<>();

keyCount.merge("A", 1, Integer::sum); // 메서드 참조 사용
keyCount.merge("A", 1, Integer::sum);
keyCount.merge("A", 1, Integer::sum);
keyCount.merge("B", 1, Integer::sum);
keyCount.merge("C", 1, Integer::sum);
keyCount.merge("C", 1, (count, increment) -> count + increment); // 람다 사용

for(String key : keyCount.keySet()) {
	System.out.println(key + " : " + keyCount.get(key));
}

-- 출력 결과 --
A : 3
B : 1
C : 2

 

 

메서드 참조 유형

메서드 참조 유형  메서드 참조  람다
정적 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]

 

 

정리

  • 메서드 참조는 람다의 간단명료한 대안이 될 수 있다.
  • 메서드 참조 쪽이 짧고 명확하다면 메서드 참조를 쓰고, 그렇지 않을 때만 람다를 사용하라

 

 

궁금한점

1. 어떤 람다에서는 매개변수의 이름 자체가 프로그래머에게 좋은 가이드가 되기도 한다. 이런 람다는 길이는 더 길지만 메서드 참조보다 읽기 쉽고 유지보수도 쉬울수 있다.

  • 아래의 예의 경우, 람다를 사용한 경우가 좀 더 읽기 쉽고 명확해 더 좋은 코드인것 같다.
Consumer<String> printString1 = (str) -> System.out.println(str);  // 람다 사용
Consumer<String> printString2 = System.out::println;               // 메서드 참조

 

 

2. 한정적 메서드 참조와 비한정적 메서드 참조의 차이점

1) 비한정적 메서드 참조

  • 비한정적(unbound) 메서드 참조는 static 메서드를 참조하는 것과 유사
  • 비한정적이라는 표현은 작성하는 구문 자체가 특정한 객체를 참조하기 위한 변수를 지정하지 않는다는 의미
  • String 클래스의 toUpperCase 메서드는 public 메서드이며 static이 아니기 때문에 반드시 String 클래스가 객체화되어야만 호출 할 수 있다. 그러나, 마치 static 메서드를 참조하는 것처럼 정의
// 비한정적 메서드 참조 
String::toUpperCase
  • 람다로 표현한 내용을 풀어서 보면, 객체의 생성을 파라미터로 받음
  • 즉, 람다 표현식 내부에서 객체 생성이 일어났기 때문에 객체를 참조할만한 변수가 외부에 존재하지 않는다.
// 람다로 표현 
(String str) -> str.toUpperCase()
  • 처리해야 하는 데이터가 여러개라면 조금 복잡해질 수 있음 List에서 연속된 2개의 데이터를 뽑아서 크기를 구하는 방식을 작성하면 다음과 같다. 매우 함축적이라, 이해하고 해석하는데 어려울 수 있다.
List<String> list =new ArrayList<>(); 

// 동일 표현
list.stream().sorted((String a, String b) -> a.compareTo(b)); 
list.stream().sorted(String::compareTo);

 

2) 한정적 메서드 참조

  • 한정적(bound)이라는 단어를 사용한 이유는 참조하는 메서드가 특정 객체의 변수로 제한되기 때문
Calendar.getInstance()::getTime

Calendar cal = Calendar.getInstance();  // 객체 생성
() -> cal.getTime()  // 람다

Calendar cal = Calendar.getInstance();  // 객체 생성
cal::getTime  // 메서드 참조 구문. cal 변수를 참조

 

3) 결론

한정적 메서드 참조는 외부에서 정의한 객체의 메서드를 참조할 때 사용하며, 비한정적 메서드 참조는 람다 표현식 내부에서 생성한 객체의 메서드를 참조할 때 사용한다.