메서드 참조
람다가 익명 클래스보다 나은 점 중에서 가장 큰 특징은 간결함이다. 그런데 자바에는 함수 객체를 메서드 참조로 더 간결하게 만들 수 있다.
예시
list.forEach(System.out::println); // 람다 대신 메서드 참조
자바 8 Map.merge() 메서드
자바 8 때 Map에 추가된 merge 메서드로 살펴보자.
Map.merge(K key, V value, BiFunction<? super V, ? super V, ? extends V> remappingFunction)
key가 없으면 value를 삽입하고, key가 이미 있으면 remappingFunction으로 기존 값과 새 값을 병합한다.
람다 사용
wordCounts.merge(word, 1, (oldVal, newVal) -> oldVal + newVal);
연산 로직을 코드 안에 명시한다.
메서드 참조를 사용할 때
wordCounts.merge(word, 1, Integer::sum);
람다 대신 이미 정의된 메서드 이름만 전달하기 때문에 더 간결하다.
언제 람다, 언제 메서드 참조?
메서드 참조를 추천하는 경우
- 이미 잘 알려진 메서드를 단순히 호출만 할 때
- 코드가 더 짧고 의도를 명확히 전달 가능할 때
- ex: Integer::sum, String::concat, Objects::isNull
람다 추천
로직이 단순 메서드 호출이 아니거나 커스텀 연산이 필요할 때
(oldVal, newVal) -> oldVal > newVal ? oldVal : newVal
람다가 더 간결한 경우
람다가 메서드 참조보다 더 간결하거나 적합한 경우가 있다.
메서드와 람다가 같은 클래스에 있을 때
import java.util.*;
public class Example {
public void run() {
List<String> list = Arrays.asList("a", "bbb", "cc");
// 같은 클래스의 메서드 process를 호출
list.forEach(s -> process(s)); // 람다가 간결하고 자연스러움
// 메서드 참조도 가능은 하지만 오히려 덜 직관적
list.forEach(this::process);
}
private void process(String s) {
System.out.println("Processing: " + s);
}
}
람다를 사용하면 호출 흐름이 명확히 코드에 드러난다.
s -> process(s)
기능은 같지만 this:: 구문이 오히려 낯설거나 불필요할 수 있다.
list.forEach(this::process);
람다 쪽이 간결한 경우
람다가 단순히 "한 줄 호출/인자 변형"을 할 때 표현이 자연스럽다.
list.forEach(s -> log(process(s)));
하지만 "같은 클래스 메서드 호출" "인자 가공/결합/조건 추가"가 있으면, 메서드 참조보다 람다가 더 읽기 쉽고 직관적이다.
메서드 참조 유형
메서드 참조의 유형은 5가지가 있는데, 각각 간단한 예제와 함께 정리해보자.
1. 정적 메서드 참조
정적 메서드 참조 형식은 다음과 같다.
클래스명::static메서드명람다와 비교하면 다음과 같다.
// 정적 메서드 참조
Function<String, Integer> f1 = Integer::parseInt;
// 람다
Function<String, Integer> f2 = s -> Integer.parseInt(s);
2. 한정적 메서드 참조
한정적 메서드 참조 형식은 다음과 같다.
참조변수::인스턴스메서드명
람다와 비교하면 다음과 같다.
String str = "hello";
// 한정적 메서드 참조
Supplier<String> f1 = str::toUpperCase;
// 람다
Supplier<String> f2 = () -> str.toUpperCase();
한정적 참조는 정적 메서드 참조와 비슷하다.
한정적 참조는 함수 객체가 받는 인수와 참조되는 메서드의 인수가 그대로 일치한다.
정적 메서드 참조
Integer::sum
// (x, y) -> Integer.sum(x, y)
한정적 참조
someString::toUpperCase
// () -> someString.toUpperCase()
- 수신 객체가 이미 한정되어 있다. (예: Integer, someString)
- 함수 객체의 파라미터와 메서드 파라미터가 같다.
3. 비한정적 메서드 참조
비한정적 메서드 참조 형식은 다음과 같다.
클래스명::인스턴스메서드명람다와 비교하면 다음과 같다.
// 비한정적 메서드 참조
Comparator<String> c1 = String::compareToIgnoreCase;
// 람다
Comparator<String> c2 = (s1, s2) -> s1.compareToIgnoreCase(s2);
함수 객체를 적용하는 시점에 수신 객체(this)를 전달한다.
String::compareToIgnoreCase
// (s1, s2) -> s1.compareToIgnoreCase(s2)
String::compareToIgnoreCase 같은 비한정적 메서드 참조는
함수 객체를 호출할 때 첫 인자가 수신 객체(this)로 쓰이고, 나머지 인자는 메서드 파라미터로 전달된다.
4. 클래스 생성자 참조
클래스 생성자 참조 형식은 다음과 같다.
클래스명::new람다와 비교하면 다음과 같다.
// 클래스 생성자 참조
Supplier<List<String>> f1 = ArrayList::new;
// 람다
Supplier<List<String>> f2 = () -> new ArrayList<>();
팩토리처럼 생성자로 객체를 만들어 준다.
import java.util.function.Supplier;
import java.util.function.Function;
class Person {
String name;
Person() { this.name = "Unknown"; }
Person(String name) { this.name = name; }
}
// Supplier: 인자 없이 객체 생성
Supplier<Person> supplier = Person::new;
Person p1 = supplier.get(); // new Person()
// Function: String -> Person 생성
Function<String, Person> function = Person::new;
Person p2 = function.apply("Alice"); // new Person("Alice")
여기서 Supplier, Function은 팩토리 인터페이스처럼 동작한다.
Supplier<Person> supplier = Person::new: supplier.get() 호출하면 객체를 생성한다.Function<String, Person> function = Person::new:function.apply("Alice")호출하면 생성자에 인자를 넣어 생성한다.
이처럼 생성자 참조는 팩토리 메서드와 동일한 역할을 하는 함수 객체를 만들어 준다.
5. 배열 생성자 참조
배열 생성자 참조 형식은 다음과 같다.
타입[]::new람다와 비교하면 다음과 같다.
// 배열 생성자 참조
IntFunction<int[]> f1 = int[]::new;
// 람다
IntFunction<int[]> f2 = size -> new int[size];
배열 생성자 참조도 “팩터리”처럼 동작한다.
배열 생성자 참조(타입[]::new)도 new 연산자를 함수형 인터페이스에 캡슐화하여 배열 팩터리처럼 동작한다.
int[] arr = f1.apply(5); // new int[5]
여기서 f1은 int[]를 생성하는 함수 객체이다.
- 생성 로직은 캡슐화
- 호출부는 apply(5)만 하면 된다. (팩터리 메서드처럼 동작)
예를 들어, Stream API에서
String[] array = list.stream().toArray(String[]::new);
String[]::new이 배열 생성 팩터리 역할을 한다.- 스트림이 몇 개의 요소인지 미리 알 수 없지만, toArray는
String[]::new팩터리에 크기만 넘기면 배열을 생성한다.
결론
메서드 참조 쪽이 짧고 가독성이 좋을 수 있다. (그렇지 않을 때만 람다를 사용하라.)
'Dev Books > Effective Java' 카테고리의 다른 글
| [item 45] 스트림은 주의해서 사용하라 (4) | 2025.07.09 |
|---|---|
| [item 44] 표준 함수형 인터페이스를 사용하라 (0) | 2025.07.01 |
| [item 42] 익명 클래스보다는 람다를 사용하라. (0) | 2025.06.30 |
| [item 41] 정의하려는 것이 타입이라면 마커 인터페이스를 사용하라 (0) | 2025.06.26 |
| [item 40] 애너테이션을 일관하게 사용하라. (0) | 2025.06.26 |
댓글