- 기존에 이미 선언되어 있는 메서드를 지정하여 인자로 넘기고 싶을 때 사용.
- :: 오퍼레이터 사용
- 사용할 메서드의 매개변수 타입과 리턴 타입을 숙지해야 생략되었을 때 혼동이 없다.
자바의 메서드 레퍼런스는 기존에 작성된 메서드를 함수형 인터페이스로 활용하기 위해 제공되는 문법처럼 느껴진다. 기존에 OOP로 작성된 코드를 혹은 패키지에 제공되는 메서드를 코드의 변경 없이 함수형 인터페이스로 변환해 사용할 수 있도록 제공해주는 것 같다.
또한 어느 정도의 자바에 익숙한 개발자여야 한다. java.util.function에 정의된 여러 함수형 인터페이스로 변환하기 위해선 기존 사용되던 메서드의 반환 타입, 파라미터의 타입을 숙지하여야 능동적으로 사용할 수 있을 것이다.
1. 클래스의 static method를 method Reference로 만들 경우.
Integer 클래스의 static method인 parseInt의 스펙은 다음과 같다.
public static int parseInt(String s) throws NumberFormatException {
return parseInt(s,10);
}
String타입의 인자를 받고, return타입으로 int를 반환한다. 이 경우 우리는 Function <T, R>를 생각해볼 수 있다.
Function <T, R> 함수형 인터페이스를 보면 T타입의 인자를 받아 R타입의 리턴을 하는 구조이다. 이 parseInt 경우 이에 해당되기 때문에 다음과 같이 활용할 수 있다.
@FunctionalInterface
public interface Function<T, R> {
R apply(T t);
}
int result = Integer.parseInt("30");
Function<String, Integer> strToInt = Integer::parseInt;
Integer result2 = strToInt.apply("123");
다음과 같이 해당 형식은 ClassName::staticMethodName으로 지정한다.
2. 선언된 객체의 instance method를 지정할 때
선언된 객체란 다음을 말한다. String str = "hello world";
String의 인스턴스에는 equals라는 문자열 비교 메서드가 존재한다. 이를 메서드 레퍼런스로 활용해보자.
String str = "hello";
Predicate<String> equalsToString = str::equals;
하나의 타입과 그의 결과 값으로 true/false를 리턴하는 Predicate를 활용할 수 있으며, Predicate.test(T t)를 통해 결과 값을 전달받을 수 있다.
이번에는 산술 연산을 하는 헬퍼 메서드를 만들고 참조로 함수를 넘겨보자.
산술 연산의 조건은 인자 두 개와 리턴 타입 모두 숫자 타입이어야 한다.
이는 BiFuntion을 상속받은 BinaryOperator를 활용할 것이다.
@FunctionalInterface
public interface BinaryOperator<T> extends BiFunction<T,T,T> {}
public static int calculate(int x, int y, BinaryOperator<Integer> operator){
return operator.apply(x,y);
}
그럼 기존에 복습하는 개념으로 calculate메서드의 두 개의 인자와 함수를 넘길 것이다. 람다식으로 우선 더하기를 진행해 볼 것이다.
calculate( 10, 20, (x, y) -> x + y);
하지만 이는 메서드 레퍼런스의 예제랑 어울리지 않다. 곱셈을 하는 메서드를 클래스의 static method로 선언해보자.
Class Example
public static int multiply(int x, int y) {
return x * y;
}
calculate(10, 20, Example::multiply);
그럼 선언된 객체의 메서드를 활용하기 위해 해당 클래스에 instance method를 선언해 넘겨보자.
Class Example
public int subtract(int x, int y) {
return x - y;
}
Example example = new Example();
calculate(10, 20, example::subtract);
그리 어렵지 않다. 이번에는 클래스의 instace method 안에서 Functional Interface를 인자로 받는 내부 메서드를 실행시켜보자.
Class Example
public static int calculate(int x, int y, BinaryOperator<Integer> operator){
return operator.apply(x,y);
}
public int subtract(int x, int y) {
return x - y;
}
public innerMethod(){
calculate(10,30, this::subtract);
}
this 예약어로 두 개의 int형의 인자를 받고 int타입으로 리턴하는 subtract메서드를 지정해 calculate를 호출할 수 있다.
즉 기본 작성된 메서드의 형태에 따라 우리는 적절한 Functinal Interface를 선택하여 활용할 수 있어야 한다.
이는 legacyMethod 또한 변형 없이 함수형 프로그래밍에 녹여낼 수 있음을 뜻한다.
3. 객체의 instance method를 지정할 때
선언된 객체의 instance method를 사용할 때는 objectName::methodName을 사용한 반면에 객체의 instance method를 사용할 때는 static method를 활용한 것과 같이 className::methodName을 사용한다.
만약 List로 특정 객체들이 있는 배열을 순회하면서 내부 프로퍼티에 접근하는 경우 일반적으로 우리는 getter를 사용한다. 그럼 이 로직은 특정 프로퍼티에 종속되게 되며 재활용할 수 없게 된다.
함수형은 함수를 인자로 넘김으로써 실행하는 로직을 갈아 끼울 수 있기 때문에 다음과 같이도 활용이 가능하다.
List<User> users = ....
printUserField(users, User::getId);
printUserField(users, User::getName);
public static void printUserField(List<User> users, Function<User, Object> getter) {
for (User user : users) {
log.info("getter apply {}", getter.apply(user));
}
}
순회를 하면서 id를 처리해야 하는 경우 User::getId를 넘겨 값을 받고, name이 필요한 경우 User::getName을 통해 이름만 받아 처리를 할 수 있는 재활용이 쉬운 메서드를 간단히 만들 수 있다.
4. 클래스의 constructor를 지정할 때
마지막은 생성자를 참조하여 넘길 때사용한다.
User user = new User(1L, "Mario");
BiFunction<Long, String, User> userConstructor = User::new;
User charlie = userConstructor.apply(2L, "Charlie");
기존에 객체를 생성하는 방식과 다르게 생성자를 함수로 할당하여 참조 형태로 가지고 있다가 Functional Interface에 호출 방식으로 호출하여 생성된 객체를 반환받는 방식이다. 객체를 생성하는 함수를 가진다는 것 많으로도 많은 활용이 가능하다.
'JAVA > [JAVA] Stream' 카테고리의 다른 글
Stream (3) (0) | 2022.02.16 |
---|---|
Stream (2) (0) | 2022.02.15 |
Stream (0) | 2022.02.12 |
Functional Interface (2) (0) | 2022.02.08 |
Functional Interface (1) (0) | 2022.02.07 |
댓글