: 메서드 참조를 이용하면 기존의 메서드 정의를 재활용해서 람다처럼 전달할 수 있다.
: 때로는 람다 표현식보다 메서드 참조를 사용하는 것이 더 가독성이 좋으며 자연스러울 수 있다.
: 다음은 메서드 참조와 새로운 자바 8 API를 활용한 정렬 예제다.
inventory.sort(comparing(Apple::getWeight));
Java
복사
1. 요약
: 메서드 참조가 왜 중요할까? 메서드 참조는 특정 메서드만을 호출하는 람다의 축약형이라고 생각하면 편하다.
: 예를 들어 람다가 ‘이 메서드를 호출해’라고 명령한다면 메서드를 어떻게 호출해야 하는지 설명을 참조하기보다는 메서드명을 직접 참조하는 것이 편리하다.
: 실제로 메서드 참조를 이용하면 기존에 구현되어있는 메서드를 이용해 람다 표현식을 만들 수 있다. 이때 명시적으로 메서드명을 참조함으로써 가독성을 높일 수 있다.
: 메서드 참조를 어떻게 사용할까? 메서드명 앞에 구분자(::)를 붙이는 방식으로 메서드 참조를 활용할 수 있다.
: 예를 들어서 Apple::getWeight은 Apple 클래스에 정의되어 있는 getWeight의 메서드 참조형태이다.
⇒ 즉 (Apple a) → a.getWeight() ⇒ Apple::getWeight로 축약한 것이다.
람다 | 메서드 참조 |
(Apple apple) → apple.getWeight() | Apple::getWeight |
() → | Thread.currentThread()::dumpStack |
Thread.currentThread().dumpStack() | |
(str, i) → str.substring(i) | String::substring |
(String s) → System.out.println(s) | System.out::println |
(String s) → this.isValidName(s) | this::isValidName |
메서드 참조를 만드는 방법
: 메서드 참조는 세가지 유형으로 구분가능하다.
1.
정적 메서드 참조
: Integer의 parseInt 메서드는 Integer::parseInt로 표현할 수 있다.
2.
다양한 형식의 인스턴스 메서드 참조
: String의 length 메서드는 String::length로 표현할 수 있다.
3.
기존 객체의 인스턴스 메서드 참조
: Transaction 객체를 할당받은 expensiveTransaction 지역 변수가 있고, Transaction 객체에 getValue 메서드가 있다면 이를 expensiveTransaction::getValue라고 표현할 수 있다.
: 두 번째 유형의 메서드 참조는 다음과 같이 사용할 수 있다.
: String::length 메서드 참조를 이용해서 람다 표현식의 파라미터로 전달할 수 있다.
: (String s) → s.toUpperCase()라는 람다 표현식을 String::toUpperCase로 줄여서 표현할 수 있다.
: 세 번째 유형의 메서드 참조는 다음과 같이 사용할 수 있다.
: 람다 표현식에서 현존하는 외부 객체의 메서드를 호출할 때 사용한다. () → expensiveTransaction.getValue() → expensiveTransaction::getValue
: 컴파일러는 람다 표현식의 형식을 검사하던 방식과 유사한 과정으로 메서드 참조가 주어진 함수형 인터페이스와 호환하는지 확인한다. 즉 메서드 참조는 컨텍스트의 형식과 일치해야한다.
2. 생성자 참조
: ClassName::new처럼 클래스명과 new 키워드를 이용해서 기존 생성자의 참조를 만들 수 있다.
Supplier<Apple> c1 = Apple::new;
Apple a1 = c1.get(); // Supplier의 get 메서드를 호출해서 새로운 Apple 객체를 만들 수 있다.
// 위 예제는 다음 코드와 동일하다.
Supplier<Apple> c1 = () -> new Apple(); // 람다 표현식은 디폴트 생성자를 가진 Apple을 만든다.
Apple a1 = c1.get(); // Supplier의 get 메서드를 호출해서 새로운 Apple 객체를 만들 수 있다.
Java
복사
: Apple(Integer weight)라는 시그니처를 갖는 생성자는 Function 인터페이스의 시그니처와 같다.
Function<Integer, Apple> c2 = Apple::new; // Apple(Integer weight)의 생성자 참조
Apple a2 = c2.apply(110); // Function의 apply 메서드에 무게를 인수로 호출해서 새로운 Apple 객체를 만들 수 있다.
// 위 예제는 다음 코드와 동일하다.
Function<Integer, Apple> c2 = (weight) -> new Apple(weight); // 특정 무게의 사과를 만드는 람다 표현식
Apple a2 = c2.apply(110); // Function의 apply 메서드에 무게를 인수로 호출해서 새로운 Apple 객체를 만들 수 있다.
Java
복사
: 다음 코드에서 Integer를 포함하는 리스트의 각 요소를 우리가 정의했던 map 같은 메서드를 이용해서 Apple 생성자로 전달한다. 결과적으로 다양한 무게를 포함하는 사과 리스트가 만들어진다.
List<Integer> weights = Arrays.asList(7, 3, 4, 10);
List<Apple> apples = map(weights, Apple::new); // map 메서드에 생성자 참조를 인수로 전달
public List<Apple> map(List<Integer> list, Function<Integer, Apple> f) {
List<Apple> result = new ArrayList<>();
for (Integer i: list) {
result.add(f.apply(i));
}
return result;
}
Java
복사
: Apple(String color, Integer weight)처럼 두 인수를 갖는 생성자는 BiFunction 인터페이스와 같은 시그니처를 가지므로 다음처럼 사용할 수 있다.
BiFunction<Color, Integer, Apple> c3 = Apple::new; // Apple(String color, Integer weight) 생성자를 참조
Apple a3 = c3.apply(GREEN, 110); // BiFunction의 apply 메서드에 색과 무게를 인수로 제공하여 새로운 사과를 만들 수 있다.
// 위 코드는 다음과 같다.
BiFunction<Color, Integer, Apple> c3 = (color, weight) -> new Apple(color, weight); // 특정 색과 무게를 가진 사과를 만드는 생성자
Apple a3 = c3.apply(GREEN, 110); // BiFunction의 apply 메서드에 색과 무게를 인수로 제공하여 새로운 사과를 만들 수 있다.
Java
복사
: 인스턴스화하지 않고도 생성자에 접근할 수 있는 기능을 다양한 상황에 응용할 수 있다.
: 예를 들어 Map으로 생성자와 문자열값을 관련시킬 수 있다. 그리고 String과 Integer가 주어졌을 때 다양한 무게를 갖는 여러 종류의 과일을 만드는 giveMeFruit라는 메서드를 만들 수 있다.
static Map<String, Function<Integer, Fruit>> map = new HashMap<>();
static {
map.put("apple", Apple::new);
map.put("orange", Orange::new);
// 등등
}
public static Fruit giveMeFruit(String fruit, Integer weight) {
return map.get(fruit.toLowerCase()).apply(weight);
}
Java
복사