람다 표현식은 사실 함수형 인터페이스의 유일한 추상 메서드인 메서드의 구현이라는 것을 보았습니다. 때때로 사람들은 이러한 람다 표현식을 “익명 메서드”라고 부르는데, 이는 이름이 없고 애플리케이션에서 이동하거나 필드 또는 변수에 저장하고 메서드나 생성자에 인수로 전달하고 메서드에서 반환할 수 있는 메서드이기 때문입니다.
때로는 다른 곳에 정의된 특정 메서드를 호출하는 람다 표현식을 작성할 때가 있습니다. 실제로 다음 코드를 작성할 때 이미 그렇게 했습니다:
Consumer<String> printer = s -> System.out.println(s);이렇게 작성된 이 람다 표현식은 System.out 객체에 정의된 println() 메서드에 대한 참조일 뿐입니다.
여기서 method reference 구문이 등장합니다.
첫 번째 메서드 참조
람다 표현식이 기존 메서드에 대한 참조일 때도 있습니다. 이 경우 method reference 로 작성할 수 있습니다. 그러면 이전 코드는 다음과 같이 됩니다:
Consumer<String> printer = System.out::println;메서드 참조에는 네 가지 범주가 있습니다:
- 정적 메서드 참조
- 바인딩된 메서드 참조
- 바인딩되지 않은 메서드 참조
- 생성자 메서드 참조
printer consumers는 언바인딩된 메서드 참조 카테고리에 속합니다.
대부분의 경우 특정 람다 표현식을 메서드 참조로 작성할 수 있는지 여부는 IDE에서 알려줄 것입니다. 주저하지 말고 물어보세요!
언바운드, 바운드 요약 및 설명
| 구분 | 설명 | 예 |
|---|---|---|
| 바운드 메서드 | 이미 존재하는 외부 객체의 메서드를 참조하는 것 | (String s) → s.toUpperCase() ⇒ String::toUpperCase |
| 언바운드 메서드 | 매개변수 중 하나로 제공될 객체의 메서드를 참조하는 것 | () → expensiveTransaction.getValue() ⇒ expensiveTransaction::getValue |
public static void main(String[] args) throws FileNotFoundException {
Supplier<List<String>> newListOfStrings = () -> new ArrayList<>();
//언바운드
Consumer<String> consumer = String::toUpperCase;
consumer.accept("");
//바운드
Supplier<List<String>> consumer2 = newListOfStrings::get;
consumer2.get();
}정적 메서드 참조 작성
다음과 같은 코드가 있다고 가정해 보겠습니다:
DoubleUnaryOperator sqrt = a -> Math.sqrt(a);이 람다 표현식은 사실 정적 메서드 Math.sqrt()에 대한 참조입니다. 그런 식으로 작성할 수 있습니다:
DoubleUnaryOperator sqrt = Math::sqrt;이 특정 메서드 참조는 정적 메서드를 참조하므로 static method reference 라고 합니다. 정적 메서드 참조의 일반적인 구문은 RefType::staticMethod입니다.
정적 메서드 참조는 둘 이상의 인수를 받을 수 있습니다. 다음 코드를 살펴봅시다:
IntBinaryOperator max = (a, b) -> Integer.max(a, b);메서드 참조를 사용하여 다시 작성할 수 있습니다:
IntBinaryOperator max = Integer::max;
언바운드 메서드 참조 작성
인수를 받지 않는 메서드
다음과 같은 코드가 있다고 가정해 보겠습니다:
Function<String, Integer> toLength = s -> s.length();이 함수는 ToIntFunction<T>로 작성할 수 있습니다. 이는 String 클래스의 메서드 length()에 대한 참조일 뿐입니다. 따라서 메서드 참조로 작성할 수 있습니다:
Function<String, Integer> toLength = String::length;이 구문은 정적 호출처럼 보이기 때문에 처음에는 혼란스러울 수 있습니다. 그러나 실제로는 그렇지 않습니다. length() 메서드는 String 클래스의 인스턴스 메서드입니다.
이러한 메서드 참조를 사용하여 일반 Java 빈에서 모든 게터를 호출할 수 있습니다. User 클래스에 getName()이 정의되어 있다고 가정해 보겠습니다. 그러면 다음 함수를 작성할 수 있습니다:
Function<User, String> getName = user -> user.getName();다음 방법을 참조하세요:
Function<User, String> getName = User::getName;인수를 받는 메서드
다음은 이미 보셨던 또 다른 예입니다:
BiFunction<String, String, Integer> indexOf = (sentence, word) -> sentence.indexOf(word);이 람다는 실제로는 String 클래스의 indexOf() 메서드에 대한 참조이므로 다음과 같이 메서드 참조로 작성할 수 있습니다:
BiFunction<String, String, Integer> indexOf = String::indexOf;이 구문은 매우 간단한 예제인 String::length 또는 User::getName보다 더 혼란스러워 보일 수 있습니다. 고전적인 방식으로 작성된 람다를 정신적으로 재구성하는 좋은 방법은 이 메서드 참조의 유형을 확인하는 것입니다. 그러면 이 람다가 받는 인수가 무엇인지 알 수 있습니다.
바인딩되지 않은 메서드 참조의 일반적인 구문은 다음과 같습니다: RefType:instanceMethod, 여기서 RefType은 타입의 이름이고 instanceMethod는 인스턴스 메서드의 이름입니다.
바운드 메서드 참조 작성
첫 번째로 본 메서드 참조의 예는 다음과 같습니다:
Consumer<String> printer = System.out::println;이 메서드 참조를 bound method reference 라고 합니다. 메서드가 호출되는 객체가 메서드 참조 자체에 정의되어 있기 때문에 이 메서드 참조를 bound 라고 합니다. 따라서 이 호출은 메서드 참조에 지정된 객체에 bound 됩니다.
unbound 구문을 고려하면: Person::getName을 살펴보면 메서드가 호출되는 객체가 이 구문의 일부가 아니라 람다 표현식의 인수로 제공된다는 것을 알 수 있습니다. 다음 코드를 살펴보세요:
Function<User, String> getName = User::getName;
User anna = new User("Anna");
String name = getName.apply(anna);함수에 전달된 User의 특정 인스턴스에 함수가 적용되는 것을 볼 수 있습니다. 그러면 이 함수는 해당 인스턴스에서 작동합니다.
이전 consumer 예제에서는 메서드 참조의 일부인 println() 메서드가 System.out 객체에서 호출됩니다.
바인딩된 메서드 참조의 일반적인 구문은 다음과 같습니다: 여기서 expr은 객체를 반환하는 표현식이고 instanceMethod는 인스턴스 메서드의 이름입니다.
생성자 메서드 참조 작성
마지막으로 알아야 할 메서드 참조 유형은 constructor method reference 입니다. 다음과 같은 Supplier<List<String>>가 있다고 가정해 보겠습니다:
Supplier<List<String>> newListOfStrings = () -> new ArrayList<>();나머지 부분과 같은 방식으로 ArrayList의 빈 생성자에 대한 참조로 요약할 수 있습니다. 메서드 참조는 그렇게 할 수 있습니다. 하지만 생성자는 메서드가 아니므로 메서드 참조의 또 다른 범주에 속합니다. 구문은 다음과 같습니다:
Supplier<List<String>> newListOfStrings = ArrayList::new;다이아몬드 연산자가 여기에 필요하지 않음을 알 수 있습니다. 넣으려면 유형도 제공해야 합니다:
Supplier<List<String>> newListOfStrings = ArrayList<String>::new;메서드 참조의 유형을 모르면 정확히 어떤 기능을 하는지 알 수 없다는 사실에 유의해야 합니다. 다음은 예시입니다:
Supplier<List<String>> newListOfStrings = () -> new ArrayList<>();
Function<Integer, List<String>> newListOfNStrings = size -> new ArrayList<>(size);변수 newListOfStrings와 newListOfNStrings는 모두 동일한 구문 ArrayList::new로 작성할 수 있지만 동일한 생성자를 참조하지는 않습니다. 이 점만 주의하면 됩니다.
메서드 참조 마무리
다음은 네 가지 유형의 메서드 참조입니다.
| Name | Syntax | Lambda equivalent |
|---|---|---|
| Static | RefType::staticMethod | (args) -> RefType.staticMethod(args) |
| Bound | expr::instanceMethod | (args) -> expr.instanceMethod(args) |
| Unbound | RefType::instanceMethod | (arg0, rest) -> arg0.instanceMethod(rest) |
| Constructor | ClassName::new | (args) -> new ClassName(args) |