메소드의 참조는 말 그대로 메소드를 참조해서 매개 변수의 정보 및 리턴 타입을 알아내어, 람다식에서 불필요한 매개 변수를 제거하는 것이 목적입니다. 람다식은 종종 기존 메소드를 단순히 호출만 하는 경우가 많습니다. 예를 들어 두 개의 값을 받아 큰 수를 리턴하는 Math 클래스의 max() 정적 메소드를 호출하는 람다식은 다음과 같습니다.
(left, right) -> Math.max(left, right);
람다식은 단순히 두개의 값을 Math.max() 메소드의 매개값으로 전달하는 역할만 하기 때문에 다소 불편해 보입니다. 이 경우 다음과 같이 메소드 참조를 이용하면 매우 깔끔하게 처리할 수 있습니다.
Math :: max; <- 메소드 참조
메소드의 참조도 람다식과 마찬가지로 인터페이스의 익명 구현 객체로 생성되므로 타겟 타입인 인터페이스의 추상 메소드가 어떤 매개 변수를 가지고, 리턴 타입이 무엇인가에 따라 달라집니다. IntBinaryOperator 인터페이스는 두 개의 int 매개값을 받아 int 값을 리턴하므로 Math :: max 메소드 참조를 대입 할 수 있습니다.
// 메소드 참조
IntBinaryOperator operator = Math :: max;
System.out.println(operator.applyAsInt(1, 3));
메소드 참조는 정적 또는 인스턴스 메소드를 참조할 수 있고, 생성자 참조도 가능합니다.
클래스 :: 메소드
인스턴스 메소드일 경우에는 먼저 객체를 생성한 다음 참조 변수 뒤에 :: 기호를 붙이고 인스턴스 메소드 이름을 기술하면 됩니다.
참조변수 :: 메소드
아래 예제는 Calculator의 정적 및 인스턴스 메소드를 참조합니다. 람다식이 메소드 참조로 대체되는 것을 기억해야 합니다.
public class ReferenceMethod {
public static void main(String[] args) {
IntBinaryOperator operator;
// 정적 메소드 참조
operator = (x, y) -> Calculator.staticMethod(x,y);
System.out.println("결과1: " + operator.applyAsInt(2,3));
// 함수적 인터페이스의 추상 메소드와 스펙이 같아야만 메소드 참조를 통해서 익명구현 객체 생성이 가능합니다.
operator = Calculator ::staticMethod;
System.out.println("결과2: " + operator.applyAsInt(2,4));
// 인스턴스 메소드 참조
Calculator calculator = new Calculator();
operator = (x, y) -> calculator.instanceMethod(x, y);
System.out.println("결과3: " + operator.applyAsInt(3, 6));
operator = calculator :: instanceMethod;
System.out.println("결과4: " + operator.applyAsInt(1,9));
}
}
메소드는 람다식 외부의 클래스 맴버일 수도 있고, 람다식에서 제공되는 매개 변수의 맴버일 수도 있습니다. 이전 예제에서는 람다식 외부의 클래스 멤버인 메소드를 호출 하였습니다. 그러나 다음 람다식에서 제공되는 a 매개 변수의 메소드를 호출해서 b 매개 변수를 매개값으로 사용하는 경우도 있습니다.
(a,b) -> { a.instanceMethod(b); }
이것을 메소드 참조로 표현하면 아래 코드와 같습니다.
클래스 :: instanceMethod;
아래 예제는 두 문자열이 대소문자와 상관없이 동일한 알파벳으로 구성되어 있는지 비교합니다. 비교를 위해 사용된 메소드는 String의 인스턴스 메소드인 compareToIgnoreCase()
입니다. a.compareToIgnoreCase(b)를 호출될 때 사전 순으로 a가 b보다 먼저 오면 음수를, 동일하면 0을, 나중에 오면 음수를 리턴합니다. 사용된 함수적 인터페이스는 두 String 매개값을 받고 int 값을 리턴하는 ToBiFunction<String, String> 입니다.
import java.util.function.ToIntBiFunction;
public class ArgumentMethodReferencesExample {
public static void main(String[] args) {
ToIntBiFunction<String, String> function;
function = (a, b) -> a.compareToIgnoreCase(b);
print(function.applyAsInt("Java8", "JAVA8"));
function = String::compareToIgnoreCase;
print(function.applyAsInt("Java8", "JAVA8"));
}
public static void print(int order){
if(order < 0){
System.out.println("사전순으로 먼저 옵니다.");
}else if(order == 0){
System.out.println("동일한 문자열입니다.");
}else{
System.out.println("사전순으로 나중에 옵니다.");
}
}
}
메소드 참조는 생성자 참조도 포함합니다. 생성자를 참조한다는 것은 객체 생성을 의미합니다. 단순히 메소드 호출로 구성된 람다식을 메소드 참조로 대치할 수 있듯이, 단순히 객체를 생성하고 리턴하도록 구성된 람다식은 생성자 참조로 대치할 수 있습니다. 아래 코드를 보면 람다식은 단순히 객체 생성 후 리턴만 합니다.
(a,b) -> { return new 클래스(a,b); }
이 경우, 생성자 참조로 표현하면 다음과 같습니다. 클래스 이름 뒤에 :: 기호를 붙이고 new 연산자를 기술하면 됩니다. 생성자가 오버로딩 되어 여러 개가 있을 경우, 컴파일러는 함수적 인터페이스의 추상 메소드와 동일한 매개 변수 타입과 개수를 가지고 있는 생성자를 찾아 실행합니다. 만약 해당 생성자가 존재하지 않으면 컴파일 오류가 발생합니다.
클래스 :: new
다음 예제는 생성자 참조를 이용해서 두가지 방법으로 Member 객체를 생성합니다.
하나는 Function<String, Member> 함수적 인터페이스의 Member apply(String) 메소드를 이용해서 Member 객체를 생성하였고, 다른 하나는 BiFunction<String, String, Member> 함수적 인터페이스의 Member apply(String, String) 메소드를 이용해서 Member 객체를 생성하였습니다. 생성자 참조는 두 가지 방법 모두 동일하지만,실행되는 Member 생성자가 다름을 볼 수 있습니다.
import java.util.function.BiFunction;
import java.util.function.Function;
public class ConstructorReferencesExample {
public static void main(String[] args) {
// 생성자 참조
Function<String, Member> function1 = Member::new;
// 매개 값 1개
Member member1 = function1.apply("angel");
BiFunction<String, String, Member> function2 = Member::new;
Member member2 = function2.apply("신천사", "angerl");
System.out.println(member1.getId());
System.out.println(member2.getId());
}
}
참조 문헌: 이것이 자바다