람다식으로 코드를 간결하게 만들 수 있지만, 이미 정의된 메서드를 단순히 호출만 하는 경우에는 굳이 람다로 감싸는 게 오히려 장황해질 수 있다. 이럴 때 사용할 수 있는 게 바로 메서드 참조(Method Reference)이다.
람다를 좀 더 간단하게 표현할 수 있는 문법적인 축약형이라고 보면 된다.
메서드 참조가 필요한 이유
기본적인 덧셈 연산을 람다식으로 표현하면 다음과 같다.
BinaryOperator<Integer> add1 = (x, y) -> x + y;
이걸 조금 개선해서, 별도의 add() 메서드를 호출하게 만들면 중복은 줄어들지만 여전히 람다 구문은 길다.
BinaryOperator<Integer> add2 = (x, y) -> add(x, y);
이 경우 메서드 참조를 쓰면 아래처럼 훨씬 간단해진다.
BinaryOperator<Integer> add3 = ClassName::add;
(x, y) -> add(x, y)와 같은 람다는 ClassName::add로 줄일 수 있다.
정적 메서드 참조
클래스명::정적메서드명
예: Math::max, Integer::parseInt
특정 객체의 인스턴스 메서드 참조
객체명::인스턴스메서드명
예: person::introduce
생성자 참조
클래스명::new
예: Person::new
임의 객체의 인스턴스 메서드 참조
클래스명::인스턴스메서드명
예: Person::introduce
Supplier<String> s1 = () -> Person.greeting();
Supplier<String> s2 = Person::greeting;
Person p = new Person("Kim");
Supplier<String> s1 = () -> p.introduce();
Supplier<String> s2 = p::introduce;
Supplier<Person> s1 = () -> new Person();
Supplier<Person> s2 = Person::new;
Function<Person, String> f1 = p -> p.introduce();
Function<Person, String> f2 = Person::introduce;
이때 Person::introduce는 Function<Person, String> 타입에서 apply()의 인자로 넘어온 Person 객체가 해당 메서드를 호출하게 된다.
리스트 처리
List<Person> people = List.of(new Person("Kim"), new Person("Lee"));
List<String> result = people.stream()
.map(Person::introduce)
.collect(Collectors.toList());
문자열 변환
List<String> upper = strings.stream()
.map(String::toUpperCase)
.collect(Collectors.toList());
매개변수가 있을 때
메서드 참조는 매개변수가 있어도 잘 동작한다. 예를 들어 다음과 같이 Function<String, String>을 만들 때도 사용 가능하다.
Function<String, String> f1 = name -> Person.greetingWithName(name);
Function<String, String> f2 = Person::greetingWithName;
이처럼 람다가 단순히 메서드 호출만 하고 있다면 메서드 참조로 충분히 대체 가능하다.
코드가 간결해진다.
가독성이 좋아진다.
재사용성이 높아진다.
특히 스트림 API와 함께 사용할 때 유용하다.
마무리
람다로도 충분히 표현할 수 있는 코드라 하더라도, 호출하는 메서드만 있는 경우엔 메서드 참조가 훨씬 깔끔하다. 자바에서 함수형 인터페이스와 람다를 사용할 일이 많아지는 요즘, 메서드 참조는 익혀두면 무조건 이득인 문법이다. 익숙해지면 쓸 수 있는 곳이 꽤 많다.