[Effective Java] - 람다보다는 메서드 참조를 사용하라
Item 43 : 람다보다는 메서드 참조를 사용하라
들어가며
Java 8에서 람다가 도입되면서 익명 클래스보다 간결하게 함수 객체를 만들 수 있게 되었다. 그런데 람다보다 더 간결하게 만드는 방법이 있다. 바로 메서드 참조(method reference) 다. 메서드 참조를 사용하면 람다의 간결함을 넘어 더욱 명확하고 읽기 쉬운 코드를 작성할 수 있다.
이번 아이템에서는 람다와 메서드 참조를 비교하며, 언제 메서드 참조를 사용해야 하는지, 그리고 메서드 참조의 다섯 가지 유형에 대해 깊이 있게 알아본다.
람다에서 메서드 참조로
아래 코드는 Map에 키가 없으면 1을 저장하고, 있으면 기존 값에 1을 더한다.
1
map.merge(key, 1, (count, incr) -> count + incr);
위 코드에서 람다 (count, incr) -> count + incr는 두 인수의 합을 반환할 뿐이다. 이미 Integer 클래스에는 이 기능을 하는 정적 메서드 sum이 있다.
1
2
// 메서드 참조 사용
wordCount.merge(word, 1, Integer::sum);
훨씬 간결해졌다. 매개변수를 명시하지 않아도 되고, 메서드 이름만으로 무슨 일을 하는지 명확히 알 수 있다. 메서드 참조는 람다의 좋은 대안이 될 수 있다.
메서드 참조가 항상 더 짧은 건 아니다
하지만 때로는 람다가 메서드 참조보다 간결할 때도 있다. 주로 메서드와 람다가 같은 클래스에 있을 때 그렇다.
1
2
3
4
5
// 같은 클래스 내에서
service.execute(GoshThisClassNameIsHumongous::action);
// 람다가 더 짧다
service.execute(() -> action());
클래스 이름이 길면 메서드 참조의 장점이 사라진다. 이럴 때는 람다가 더 이득이다.
메서드 참조의 다섯 가지 유형
메서드 참조에는 다섯 가지 유형이 있다. 각각의 특징과 사용 예시를 살펴보자.
1. 정적 메서드 참조 (Static Method Reference)
클래스의 정적 메서드를 바로 가져다 쓴다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 상황: 문자열 숫자들을 실제 숫자로 변환하고 싶을 때
List stringNumbers = Arrays.asList("100", "200", "300");
// 람다 방식
List numbers1 = stringNumbers.stream()
.map(str -> Integer.parseInt(str)) // parseInt를 람다로 호출
.collect(Collectors.toList());
// 정적 메서드 참조
List numbers2 = stringNumbers.stream()
.map(Integer::parseInt) // Integer 클래스의 parseInt 메서드 참조
.collect(Collectors.toList());
// 결과: [100, 200, 300]
클래스명::정적메서드 - 클래스에 딸린 도구를 바로 쓰는 것
2. 한정적 인스턴스 메서드 참조 (Bound Instance Method Reference)
이미 만들어진 특정 갹채의 기능을 사용한다.
1
2
3
4
5
6
7
8
9
10
LocalDateTime now = LocalDateTime.now(); // 현재 시각 (특정 객체)
List dates = Arrays.asList(
LocalDateTime.of(2024, 1, 1, 0, 0),
LocalDateTime.of(2025, 6, 1, 0, 0)
);
// 특정 시각 이전인지 확인
List pastDates = dates.stream()
.filter(now::isAfter) // now 객체의 isAfter 메서드 사용
.collect(Collectors.toList());
특정객체::메서드 - 이미 만들어진 특정 물건의 기능 사용
3. 비한정적 인스턴스 메서드 참조 (Unbound Instance Method Reference)
각자 자기 자신의 기능을 사용한다.
1
2
3
4
5
6
7
8
// 예시: 각 문자열의 길이 구하기
List fruits = Arrays.asList("apple", "banana", "kiwi");
List lengths = fruits.stream()
.map(String::length) // 각 문자열이 자기 길이를 반환
.collect(Collectors.toList());
// 결과: [5, 6, 4]
// "apple"이 자기 길이 5를 반환, "banana"가 자기 길이 6을 반환...
클래스명::인스턴스메서드 - 타입만 정하고, 각자 알아서 자기 메서드 호출
4. 클래스 생성자 참조 (Class Constructor Reference)
새로운 객체를 만드는 공장
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 상황: 여러 이름으로 Person 객체들을 만들고 싶을 때
class Person {
String name;
Person(String name) {
this.name = name;
}
}
List names = Arrays.asList("철수", "영희", "민수");
// 람다 방식
List people1 = names.stream()
.map(name -> new Person(name)) // 하나씩 new로 생성
.collect(Collectors.toList());
// 생성자 참조 방식
List people2 = names.stream()
.map(Person::new) // Person 생성자를 참조
.collect(Collectors.toList());
클래스명::new - 생성자를 함수처럼 전달
5. 배열 생성자 참조 (Array Constructor Reference)
특정 크기의 배열 만들기
1
2
3
4
5
6
7
8
9
10
11
List fruits = Arrays.asList("사과", "바나나", "딸기");
// 람다 방식
String[] array1 = fruits.stream()
.toArray(size -> new String[size]); // 크기에 맞는 배열 생성
// 배열 생성자 참조 - 훨씬 간단!
String[] array2 = fruits.stream()
.toArray(String[]::new); // String 배열 생성자 참조
// 결과: ["사과", "바나나", "딸기"]
타입[]::new - 크기 맞춰서 배열 생성하는 공장
메서드 참조 정리
| 유형 | 형태 | 비유 | 람다 예시 | 메서드 참조 예시 |
|---|---|---|---|---|
| 정적 | 클래스명::정적메서드 |
클래스 도구 사용 | str -> Integer.parseInt(str) |
Integer::parseInt |
| 한정적 | 특정객체::메서드 |
특정 물건 기능 사용 | t -> now.isAfter(t) |
now::isAfter |
| 비한정적 | 클래스명::인스턴스메서드 |
각자 자기 기능 사용 | str -> str.toLowerCase() |
String::toLowerCase |
| 클래스 생성자 | 클래스명::new |
물건 만드는 공장 | () -> new ArrayList<>() |
ArrayList::new |
| 배열 생성자 | 타입[]::new |
배열 만드는 공장 | len -> new String[len] |
String[]::new |
람다가 메서드 참조보다 나은 경우
메서드 참조가 항상 답은 아니다. 람다가 더 읽기 쉽고 명확한 케이스도 있다.
1. 메서드 참조가 너무 긴 경우
1
2
3
4
5
// 메서드 참조가 더 길다
service.execute(GoshThisClassNameIsHumongousAndUnwieldy::action);
// 람다가 더 간결
service.execute(() -> action());
2. 함수가 간단할 때
1
2
3
4
5
6
7
8
9
// 이 정도로 간단하면 람다도 충분히 명확
numbers.stream()
.filter(n -> n > 0)
.map(n -> n * 2);
// 메서드를 따로 만들면 오히려 복잡
numbers.stream()
.filter(MyClass::isPositive)
.map(MyClass::doubleIt);
3. 매개변수 이름이 문서 역할을 할 때
1
2
3
4
5
6
// 매개변수 이름이 의미를 전달
inventory.merge(item, 1, (oldCount, increment) -> oldCount + increment);
inventory.merge(item, 1, Integer::sum);
// sum이 무엇을 더하는지 명확하지만,
// 비즈니스 로직에서는 oldCount + increment가 더 명확할 수 있음
제네릭과 메서드 참조
제네릭 함수 타입은 메서드 참조 표현식으로만 구현 가능하다. 람다식으로는 불가능하다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// 인터페이스 메서드가 제네릭
interface Processor {
<T> T process(T input); // 메서드 자체가 제네릭
}
// 실제 구현 클래스
class StringProcessor {
public <T> T handle(T input) {
System.out.println("처리: " + input);
return input;
}
}
public class Test {
public static void main(String[] args) {
StringProcessor sp = new StringProcessor();
// 메서드 참조로 Processor 구현
Processor p = sp::handle; // handle이 제네릭이라서 가능
String result1 = p.process("hello"); // T = String
Integer result2 = p.process(123); // T = Integer
// 람다로 불가능 (문법 오류)
// Processor p2 = <T>(T input) -> {
// return input;
// };
}
}
이런 경우는 드물지만, 함수형 인터페이스의 추상 메서드가 제네릭일 수 있다는 점은 알아두자.
실무 적용 가이드
메서드 참조와 람다 중 어떤 것을 선택할지 판단하는 기준은 다음과 같다.
메서드 참조를 우선하는 경우
- 더 짧고 명확할 때 -
Integer::sumvs(a, b) -> a + b - 메서드 이름이 의도를 잘 드러낼 때 -
String::toLowerCase - 표준 API에 있는 메서드일 때 -
Math::max,Collections::sort
람다를 우선하는 경우
- 메서드 참조가 더 길 때 - 클래스 이름이 길면 람다가 나음
- 매개변수 이름이 문서 역할을 할 때 -
(name, age) -> ... - 간단한 로직일 때 -
x -> x > 0
핵심은 “더 간결하고 명확한 쪽을 선택하라” 는 것이다.
IDE의 도움
최신 IDE들은 람다와 메서드 참조 사이의 변환을 자동으로 제안한다.
IDE의 제안을 맹목적으로 따르지 말고, 실제로 더 명확해지는지 판단해야 한다. 하지만 대부분의 경우 IDE의 제안을 따르는 것이 좋다.
메서드 참조의 내부 동작
메서드 참조는 컴파일러가 적절한 람다 표현식으로 변환한다. 성능 측면에서 차이는 거의 없다.
1
2
3
4
Function f = Integer::parseInt;
// 컴파일러가 이렇게 변환
Function f = (String s) -> Integer.parseInt(s);
메서드 참조는 단순히 문법적 설탕(syntactic sugar)에 불과하다. 런타임 성능에는 영향이 없고, 오직 코드의 가독성만 향상시킨다.
마치며
메서드 참조는 람다보다 더 간결하고 명확한 코드를 작성할 수 있게 해준다. 메서드 참조로 짧고 명확하게 만들 수 있다면 메서드 참조를 사용하고, 그렇지 않다면 람다를 사용하라.
다섯 가지 메서드 참조 유형을 숙지하고, 특히 한정적과 비한정적 인스턴스 메서드 참조의 차이를 명확히 이해하는 것이 중요하다. 한정적은 특정 객체에 한정되고, 비한정적은 전달받은 객체를 수신자로 사용한다는 점을 기억하자.
IDE의 도움을 받되, 실제로 더 명확해지는지 항상 확인하고, 코드 리뷰에서 팀원들과 가독성에 대해 논의하는 것도 좋은 습관이다. 결국 가장 중요한 것은 코드를 읽는 사람이 의도를 명확히 이해할 수 있느냐 다.
References
- 이펙티브 자바 3/E

