top bar

글 목록

2017년 2월 22일 수요일

[JAVA8] 메서드 레퍼런스 탐구

메서드 레퍼런스는 Java8에 추가된 문법중에서도 참으로 오묘한 녀석이다. Java8의 가장 주요한 feature들은 다들 알겠지만 '람다'와 '스트림 API'인데, 메서드 레퍼런스는 '람다'와 궤를 같이 한다.

예컨데, '람다'식은 '함수형 인터페이스'가 파라메터로 정의된 메서드의 인수로 넘겨줄 수 있다. 아래의 예제를 보자.

public <T, R> R lambdaTest(T t, Function<T, R> func) {
    return func.apply(t);
}

여기서 'Function' 인터페이스는 추상메서드가 하나만 선언된 함수형 인터페이스이고, Java8의 기본 함수형 인터페이스들의 목록은 아래의 문서에서 확인 할 수 있다.

https://docs.oracle.com/javase/8/docs/api/java/util/function/package-summary.html

위의 메서드에 아래와 같이 람다식을 인수로 넘겨서 수행해 본다.

int result = lambdaTest(10, (val) -> val + 10);

이때 'result'의 결과는 '20'이다.

람다식이 인수로 넘겨질때, 함수형 인터페이스의 정의 되어있는 추상 메서드의 시그니처, 즉 '함수 디스크립터'를 컴파일러가 해석하고, 인수로 넘겨지는 람다식과 호환이 되는지 판별한다. 호환되지 않으면 당연히 컴파일 에러를 발생시킬 것이다.

'메서드 레퍼런스'도 이와 같다. 함수형 인터페이스와 호환하는지 확인하는 것을 거쳐서, 인자로 넘겨질 수 있다.

위의 예제에서 아래와 같은 메서드가 있다고 하자.

public int sum(int val) {
    return val + 10;
}

위의 'sum' 메서드가 'MethodReferTest' 클래스에 작성된 멤버 메서드라고 했을때,
아래와 같이 메서드 정의 자체를 인수로 넘겨 줄 수 있다.

int result = lambdaTest(10, MethodReferTest::sum);

참으로 신박하다.

람다식을 메서드 레퍼런스로 바꾸는 방법은 아래와 같이 3가지 방법이 존재한다.



사실 여기까지는 직관적으로도 이해할 수 있는 부분이다.

하지만, 메서드 오버로딩(overloading)에 관련해서 메서드 레퍼런스는 어떤 방식으로 동작 할까? 예를들어, 아래처럼 substring의 경우를 보자.

(str, i) -> str.substring(i)

위의 람다식은 ②번의 경우처럼 아래와 같은 메서드 레퍼런스로 바꿀 수 있다.

String::substring

하지만 모두가 잘 알고 있듯, substring는 2가지 종류로 오버로딩된다. 즉,
'substring(beginIndex, endIndex)' 라는 메서드도 존재한다는 말이다.

이경우, 메서드 레퍼런스가 전달되어지는 메서드의 파라메터 즉, 함수형 인터페이스의 함수 디스크립터를 판별하여 오버로딩된 메서드중에 적절한 메서드를 찾아 실행시켜 준다. 만약 오버로딩된 메서드중 기대되는 함수형 인터페이스와 호환되는 메서드가 없다면, 컴파일에러가 발생할 것이다.

설명하면 어려우니, 아래의 예제로 퉁치자.

public static void main(String[] args) {

    List<String> stringList = Arrays.asList("abcdefg", "I'm a boy", "You are a girl");

    List<String> substringList = stringList.stream()
        .map(string -> testSubstring(string, 2, 4, String::substring))
        .collect(Collectors.toList());

    System.out.println(substringList);
}

public static String testSubstring(String source, Integer beginIndex, Integer endIndex, 
    TriFunction<String, Integer, Integer, String> function) {
    return function.apply(source, beginIndex, endIndex);
}

여기서 'TriFunction' 이라는 함수형 인터페이스는 java.util.function 패키지에 존재하지 않는다. 따라서 아래와 같이 직접 인터페이스를 작성하자.

@FunctionalInterface
interface TriFunction<T, U, V, R> {
    R apply(T t, U u, V v);
}

위에서 말했다 시피, substring은 인자가 하나인것, 인자가 두개인것 이렇게 2가지로 오버로딩된다. 하지만 위 코드에서 확인할 수 있듯이, 기대되는 함수형인터페이스에 따라서, 어떠한 substring 메서드가 실행될 것인지 자동으로 매칭이 된다.

만약 기대되는 함수형 인터페이스가 BiFunction<T, U, R> 이었다면, 인자가 하나인 substring 메서드가 실행 될 것이다.

마치며...



예전부터 계속 람다와 메서드 레퍼런스, 그중에서도 오늘 포스팅한 내용을 꼭 정리하고 싶었다. 이들은 Java8에서 없어서는 안될 요소이며, 이들을 제대로 사용하지 못한다면 솔직히 Java8을 사용하는 의미가 없다. 람다와 메서드 레퍼런스 없이는 '스트림 API'를 제대로 사용하는 것은 불가능 하기 때문이다.



댓글 없음:

댓글 쓰기