메서드 참조
메서드 참조는 특정 메서드를 참조하여 매개변수와 리턴 타입을 알아낸 뒤, 람다식의 불필요한 부분을 제거하여 코드를 간결하게 만드는 기법입니다.
이를 통해 가독성을 향상하고 람다 표현식보다 더 간결한 코드를 작성할 수 있습니다.
람다식이 단 하나의 메서드를 호출한 경우에 해당 람다식에서 불필요한 매개변수를 없앨 수 있으며 이때 형식은 `클래스명::메서드명`의 형태로 작성해 간결한 메서드 참조로 변경할 수 있습니다.
메서드 참조 예시
Map 인터페이스의 merge 메서드를 예제 코드를 작성하겠습니다.

merge 메서드 설명
- 전달받은 key가 map 내 존재하지 않는다면 value 값을 (key, value) 쌍으로 저장
- 전달받은 key가 map 내 존재한다면 value 값을 BiFunction의 apply 메서드에 적용해 {key, BiFunction.apply() 메서드의 결과} 쌍으로 저장
- BiFunction 인터페이스는 하나의 추상 메서드만 가지고 있는 함수형 인터페이스이기에 람다를 이용해 구현 가능
1. 람다 표현식을 사용하는 경우
Map<String, Integer> frequencyTable = new TreeMap<>(); | |
for (String s : args) { | |
frequencyTable.merge(s, 1, (count, incr) -> count + incr); // Lambda | |
} | |
System.out.println(frequencyTable); |
2. 메서드 참조를 사용하는 경우
Map<String, Integer> frequencyTable = new TreeMap<>(); | |
for (String s : args) { | |
frequencyTable.merge(s, 1, Integer::sum); // Method reference | |
} | |
System.out.println(frequencyTable); |
이처럼 하나의 메서드를 호출하는 람다 표현식을 메서드 참조로 변경할 경우 보다 간결해지는 것을 볼 수 있습니다.
하나의 메서드를 호출하더라도 람다 표현식을 사용해야 하는 경우
메서드 참조를 사용하면 코드가 간결해지지만, 람다의 매개변수명이 개발자들에게 더 나은 가이드가 되는 경우가 있어서 코드 길이가 길어지더라도 람다 표현식을 사용하는 경우가 있습니다.
예를 들어 다음과 같이 호출하고자 하는 메서드가 엄청 긴 이름을 가진 클래스로부터 호출될 경우 가독성 측면에서 람다를 사용하는 것이 더 좋은 코드라고 할 수 있습니다.
// 메서드 참조 | |
service.execute(GoshThisClassNameIsHumongous::action); | |
// 람다 사용 | |
service.execute(() -> action()); |
코드 부연 설명
- 위 코드를 작성했을 때 Intellij와 같은 IDE들은 람다를 메서드 참조로 대체하라고 권할 것이고 대부분의 케이스에서는 IDE의 추천을 이행하는 것이 권장되지만
- 위 코드의 경우 클래스명이 너무 길기 때문에 메서드 참조로 변환하더라도 코드가 짧아지지도 않을뿐더러 명확하지도 않음
- 람다 쪽이 더 나음
메서드 참조 유형
메서드 참조 유형은 총 다섯 가지입니다.
- 정적 메서드
- 한정적 인스턴스 메서드
- 비한정적 인스턴스 메서드
- 클래스 생성자
- 배열 생성자
1. 정적 메서드
- 정적 메서드를 가리키는 메서드 참조
2. 한정적 인스턴스 메서드
- 정적 메서드 참조와 비슷하게 수신 객체를 특정
- 수신 객체는 람다의 파라미터 중 하나로 제공되는 특정 객체
- 즉, 특정 객체의 메서드를 호출하는 메서드 참조
3. 비한정적 인스턴스 메서드
- 수신 객체를 특정하지 않고 적용하는 시점에 수신 객체를 알려줌
- 이미 존재하는 외부 객체를 호출하는 상황
- 비한정적 참조는 주로 스트림 파이프라인에서의 매핑과 필터 함수에 쓰임
- 비한정적 인스턴스 메서드 참조는 특정 객체에 제한되지 않기 때문에 최상위 계층인 Object의 메서드 참조
4. 클래스 생성자
- 클래스 생성자를 가리키는 메서드 참조
- 팩터리 객체로 사용
5. 배열 생성자
- 배열의 생성자를 가리키는 메서드 참조
public class Example { | |
public static void main(String[] args) { | |
// 정적 메서드 | |
BinaryOperator<Integer> methodReference = Integer::sum; | |
BinaryOperator<Integer> lambdaExpression = (a, b) -> Integer.sum(a, b); | |
// 한정적 인스턴스 메서드 | |
Instant then = Instant.now(); | |
Predicate<Instant> boundedMethodReference = Instant.now()::isAfter; | |
Predicate<Instant> boundedLambdaExpression = t -> then.isAfter(t); | |
// 비한정적 인스턴스 메서드 | |
Function<String, Integer> unboundedMethodReference = String::length; | |
Function<String, Integer> unboundedLambdaExpression = s -> s.length(); | |
// 클래스 생성자 | |
Supplier<List<String>> constructorReference = ArrayList::new; | |
Supplier<List<String>> constructorLambdaExpression = () -> new ArrayList<>(); | |
// 배열 생성자 | |
Function<Integer, int[]> arrayConstructorReference = int[]::new; | |
Function<Integer, int[]> arrayConstructorLambdaExpression = size -> new int[size]; | |
} | |
} |
비고
람다 표현식으로 표현할 수 없으면 일반적으로 메서드 참조로도 작성할 수 없습니다.
그러나 한 가지 예외가 있는데 람다로는 제네릭 함수 타입 구현을 표현할 수 없지만 메서드 참조를 활용하여 구현이 가능합니다.
제네릭 람다식이라는 문법이 존재하지 않기 때문입니다.
interface G1 { | |
<E extends Exception> Object m() throws Exception; | |
} | |
public class Example { | |
public static void main(String[] args) { | |
// G1 인터페이스를 메서드 참조로 구현 | |
G1 g1 = Example::func; | |
} | |
public static Object func() throws RuntimeException { | |
return new Object(); | |
} | |
} |
위 코드는 메서드 참조를 사용해 제네릭 함수 타입 구현을 표현해서 컴파일 에러가 발생하지 않지만 이를 람다로 변경할 경우 컴파일 에러가 발생하는 것을 볼 수 있습니다.

참고
이펙티브 자바
'JAVA > Effective Java' 카테고리의 다른 글
[아이템 45] 스트림은 주의해서 사용하라 (0) | 2024.03.02 |
---|---|
[아이템 44] 표준 함수형 인터페이스를 사용하라 (0) | 2024.03.02 |
[아이템 42] 익명 클래스보다는 람다를 사용하라 (1) | 2024.03.02 |
[아이템 41] 정의하려는 것이 타입이라면 마커 인터페이스를 사용하라 (0) | 2024.02.29 |
[아이템 40] @Override 애너테이션을 일관되게 사용하라 (0) | 2024.02.28 |