1. 메서드 참조

분명히 앞선 아이템 41에서, 익명클래스보다는 람다를 사용하라는 내용이 있었다. 바로 간결함 때문이다. 그런데, 함수 객체를 람다보다도 더 간결하게 만드는 방법이 있었다. 바로 메서드 참조이다. 함수 객체가 뭐였는가? 예전 자바에서 함수 타입을 표현할 땐 추상메서드를 하나만 담은 인터페이스의 인스턴스를 함수 객체라고 불렀다. 이를 매우 간단하게 만드는 방법이 메서드 참조라니, 아래에서 더 알아보자. Map 의 merge 메서드를 살펴보자.
map.merge(key, 1, (count, incr) -> count + incr);
키가 맵 안에 없다면 키와 숫자 1을 매핑하고, 이미 있다면 기존 매핑 값을 증가시키는 내용이다. 아래 예시처럼 사용된다.
import java.util.HashMap;
import java.util.Map;
public class MapMergeExample {
public static void main(String[] args) {
Map<String, Integer> map = new HashMap<>();
// 초기값 설정
map.put("apple", 3);
map.put("banana", 2);
// 키 "apple"에 값 2를 추가
map.merge("apple", 2, (oldValue, newValue) -> oldValue + newValue);
System.out.println("After merging apple: " + map); // {apple=5, banana=2}
// 키 "orange"가 없으므로 새로 추가
map.merge("orange", 4, (oldValue, newValue) -> oldValue + newValue);
System.out.println("After merging orange: " + map); // {apple=5, banana=2, orange=4}
}
}
물론 깔끔해 보일 수 있지만, 매개변수인 count 와 incr , 예시에서는 oldValue 와 newValue 는 크게 하는 일이 없다. 두 인수의 합을 단순히 반환할 뿐이다. 자바 8 이후 Integer 클래스 (와 모든 기본타입의 박싱 타입) 는 람다와 기능이 같은 정적 메서드 sum 을 제공한다. 이를 메서드 참조를 이용한다고 한다. 메서드 참조를 활용해 수정해보자.
map.merge(key, 1, Integer::sum);
매개변수의 이름 자체를 사용할 수 없어 뜻을 이해하기 어렵고 유지보수가 어려울 순 있다. 그래도 메서드 참조를 사용하는 편이 더 짧고 간결하다는 강점이 있다. 그래서 보통 람다로 작성할 코드가 너무 길거나 복잡하다면 이를 새로운 메서드에 담은 다음, 람다 대신 그 메서드 참조를 사용한다.
2. 람다가 메서드 참조보다 간결할 때
IDE 는 람다를 메서드 참조로 대체하라고 권고할 것이지만, 람다가 메서드 참조보다 간결할 때도 있다. 주로 메서드와 람다가 같은 클래스에 있을 때 그렇다. 아래 두 케이스가 GoshThisClassNameIsHumongous 클래스 안에 같이 있다고 해보자.
service.execute(GoshThisClassNameIsHumongous::action);
//메서드 참조
service.execute(() -> acation());
//람다 사용
람다 쪽이 더 짧고, 명확하다.
3. 메서드 참조의 다섯가지 유형
| 메서드 참조 유형 | 예 | 같은 기능을 하는 람다 |
| 정적 | Integer::parseInt | str -> Integer.parseInt(str) |
| 한정적(인스턴스) | Instance.now()::isAfter | Instance 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] |
정적 메서드 참조는 클래스의 정적 메서드를 참조하는 방식입니다.
// 정적 메서드 참조
Function<String, Integer> func = Integer::parseInt;
System.out.println(func.apply("123")); // 출력: 123
// 람다 표현식
Function<String, Integer> func = str -> Integer.parseInt(str);
System.out.println(func.apply("123")); // 출력: 123
한정적(인스턴스) 메서드 참조는 특정 객체의 인스턴스 메서드를 참조하는 방식입니다. 예시로 나온 then::isAfter는 이미 선언된 then 인스턴스의 isAfter 메서드를 참조합니다.
// 한정적 메서드 참조
Instant then = Instant.now();
Predicate<Instant> func = then::isAfter;
System.out.println(func.test(Instant.now().minusSeconds(3600))); // true or false
// 람다 표현식
Instant then = Instant.now();
Predicate<Instant> func = t -> then.isAfter(t);
System.out.println(func.test(Instant.now().minusSeconds(3600))); // true or false
비한정적(인스턴스) 메서드 참조는 특정 인스턴스를 지정하지 않고, 클래스 타입의 인스턴스 메서드를 참조하는 방식입니다.
// 비한정적 메서드 참조
Function<String, String> func = String::toLowerCase;
System.out.println(func.apply("HELLO")); // 출력: hello
// 람다표현식
Function<String, String> func = str -> str.toLowerCase();
System.out.println(func.apply("HELLO")); // 출력: hello
클래스 생성자 참조는 특정 클래스의 생성자를 참조하여 객체를 생성하는 방식입니다.
// 클래스 생성자 참조
Supplier<TreeMap<Integer, String>> func = TreeMap::new;
TreeMap<Integer, String> map = func.get();
map.put(1, "One");
System.out.println(map); // 출력: {1=One}
// 람다 표현식
Supplier<TreeMap<Integer, String>> func = () -> new TreeMap<Integer, String>();
TreeMap<Integer, String> map = func.get();
map.put(1, "One");
System.out.println(map); // 출력: {1=One}
배열 생성자 참조는 배열의 생성자를 참조하여 배열을 생성하는 방식입니다.
// 배열 생성자 참조
IntFunction<int[]> func = int[]::new;
int[] arr = func.apply(5); // 길이가 5인 배열 생성
System.out.println(arr.length); // 출력: 5
// 람다 표현식
IntFunction<int[]> func = len -> new int[len];
int[] arr = func.apply(5); // 길이가 5인 배열 생성
System.out.println(arr.length); // 출력: 5
'1️⃣ 백앤드 > 이펙티브 자바' 카테고리의 다른 글
| [아이템45] 스트림은 주의해서 사용하라 (0) | 2024.11.14 |
|---|---|
| [아이템44] 표준 함수형 인터페이스를 사용하라 (1) | 2024.11.13 |
| [아이템42] 익명 클래스보다는 람다를 사용하라 (2) | 2024.11.11 |
| [아이템41] 정의하려는 것이 타입이라면 마커 인터페이스를 사용하라 (2) | 2024.11.07 |
| [아이템40] @Override 애너테이션을 일관되게 사용하라 (1) | 2024.11.07 |
