본문 바로가기
JAVA/[JAVA] Stream

Method Reference

by oncerun 2022. 2. 9.
반응형

 

  • 기존에 이미 선언되어 있는 메서드를 지정하여 인자로 넘기고 싶을 때 사용.
  • :: 오퍼레이터 사용
  • 사용할 메서드의 매개변수 타입과 리턴 타입을 숙지해야 생략되었을 때 혼동이 없다.

 

자바의 메서드 레퍼런스는 기존에 작성된 메서드를 함수형 인터페이스로 활용하기 위해 제공되는 문법처럼 느껴진다. 기존에 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

댓글