43. 람다보다는 메서드 참조를 사용하라
가능한 람다를 사용하자!
📌 1. 발표 전 알아야 할 개념
✅ 수신 객체(receiver object)
메서드가 호출되는 대상 객체
kim.toUpperCase()
라고 하면 -> 수신 객체는kim
✅ 람다에 대해 다시 한 번 짚고 넘어가자!
람다 : java 8 이후에 도입된, 익명 함수를 간단하게 표현하는 방식
// Consumer<T> 람다 구현
List<String> carList = List.of("Tesla", "GV70", "Porshce");
carList.forEach(car -> System.out.println(car));
📕 2. 메서드 참조를 사용하라
2-1. 메서드 참조란?
기존의 메서드 이름을 그대로 사용하는 표현 방식
간결하고, 의도를 명확하게 표현할 수 있다
위의 식을 바꿔보면
carList.forEach(System.out::println);
// System.out 객체의 println 메서드를 각각의 요소에 대해 호출하라
(예시)
// compareToIgnoreCase 구현(대소문자를 무시하고 비교)
// 람다 표현식
list.sort((s1, s2) -> s1.compareToIgnoreCase(s2));
// 메서드 참조
list.sort(String::compareToIgnoreCase);
2-2. 메서드 참조의 5가지 종류
정적 메서드를 가리키는 메서드 참조
ClassName::staticMethod
형식정적 메서드를 참조함
ex) Math::abs, Integer::parseInt
Function<String, Integer> parse = Integer::parseInt;
System.out.println(parse.apply("123"));
수신 객체를 특정하는 한정적 인스턴스 메서드 참조
instance::instanceMethod
형식수신 객체가 이미 정해져 있음
인스턴스를 대상으로 메서드 호출
ex) System.out::println
PrintStream out = System.out;
Consumer<String> printer = out::println;
수신 객체를 특정하지 않는 비한정적 인스턴스 메서드 호출
ClassName::instanceMethod
형식수신 객체가 지금은 정해져 있지 않지만, 실행시 첫 번째 인자가 수신 객체
클래스 타입 기준으로 메서드를 참조
ex) String::compareToIgnoreCase, String::equalsIgnoreCaes
List<String> names = List.of("Kim", "Lee", "Park");
names.sort(String::compareToIgnoreCase); // 각 요소끼리 비교
클래스 생성자를 가리키는 메서드 참조
ClassName::new
형식생성자를 참조
ex) ArrayList::new, HashMap::new
Supplier<List<String>> listSupplier = ArrayList::new;
List<String> list = listSupplier.get(); // 새 ArrayList 생성
배열 생성자를 가리키는 메서드 참조
Type[]::new
형식배열을 생성하는 생성자 참조
ex) String[]::new, int[]::new
IntFunction<String[]> arrayCreator = String[]::new;
String[] arr = arrayCreator.apply(5);
🤔 3. 메서드 참조의 장단점
3-1. 왜 메서드 참조를 선호해야 할까?
불필요한 매개변수, 람다 본문 생략으로 간결함
메서드를 그대로 사용하기 때문에 의도가 더 명확하게 드러난다
중복을 줄일 수 있다
3-2. 메서드 참조가 안 되는 경우?
단순한 메서드 호출이 아닌 경우
조건문이나 루프를 포함하는 경우
변수 캡쳐를 포함하는 경우
예시)
list.forEach(name -> {
log(name);
sendEmail(name);
})
메서드 참조로 바꾸기 위해서는 람다 본문을 별도의 메서드로 추출하고, 메서드를 메서드 참조로 넘겨야 한다.
람다식을 process라는 메서드로 추출하고,
public class Service {
public static void process(String name) {
log(name);
sendEmail(name);
}
private static void log(String name) {
System.out.println("Logging: " + name);
}
private static void sendEmail(String name) {
System.out.println("Sending email to: " + name);
}
}
메서드 참조로 바꾸면 된다!
list.forEach(Service::process);
💨 향후 확장 포인트
람다로 할 수 없는 일이라면 메서드 참조로도 할 수 없다(애매한 예외 단 한가지를 빼고...)
람다로 표현할 때 타입 인자를 일반적으로 생략해야하는데, 제네릭의 경우 타입을 직접 지정해야 한다. 다만 메서드 참조의 경우 문맥에서 타입을 추론할 수 있으므로 훨씬 깔끔함
public class MyUtils {
public static <T> T identity(T t) {
return t;
}
}
// 타임 명시를 안할 경우 - x의 타입이 확실하지 않음
UnaryOperator<String> identity1 = x -> MyUtils.identity(x);
// 람다 - 타입 명시를 해야함 <String>
UnaryOperator<String> identity1 = x -> MyUtils.<String>identity(x);
// 메서드 참조 - 타입 추론 가능!
UnaryOperator<String> identity2 = MyUtils::identity;
🤖 최종 결론
메서드 참조가 짧고 명확하다면 메서드 참조를 써라!
❗어려웠던 점
이전의 학습 내용을 계속 확장하면서 공부해서 이해가 안되는 부분이 있거나 헷갈리는 부분에서 이해가 바로 안됐지만, 오히려 한 번 더 정리할 수 있었다!
제네릭의 경우 메서드 참조가 유리하다는 점에서 컴파일의 해석 방식을 생각해야 하는 부분이 두개가 비슷하게 느껴져 어려웠다.
😶🌫️ 느낀점
단순히 짧게 쓰는 문법이 아니라 의도가 더 명확하게 드러나고, 가독성과 의미 전달에서 메서드 참조를 사용해야겠다고 생각했다!
Last updated