티스토리 뷰

앞선 포스팅에서 메서드를 전달하기 위한 익명 클래스를 작성하는 반복되고 지저분한 코드를 줄이는게 람다 표현식이라고 설명했다. 메서드 레퍼런스는 이름에서 알 수 있듯이 메서드의 레퍼런스를 전달한다는 의미이며, 람다표현식에서 메서드 호출 1회로 코드가 끝나는 경우 메서드 레퍼런스를 이용해 이미 줄인 코드를 더 줄일수도있다.

메서드의 레퍼런스라고하지만 엄밀히 말해 자바에서의 메서드는 일급 객체가 아니기때문에 객체의 레퍼런스를 전달하는 방식으로 작동한다.


Function<String, Integer> f = str -> Integer.parseInt(str);

Function 인터페이스는 하나의 인자를 받아 다른타입의 리턴타입을 갖는 apply() 메서드를 갖고있는 함수형 인터페이스이다. 람다를 사용해 String 객체를 인자로 받아 Integer 타입으로 변환하도록 구현했다. 메서드 구현부분이 Integer.parseInt() 메서드 한번 호출로 끝나버린다. 이럴때 메서드 레퍼런스를 사용할 수 있다.


Function<String, Integer> f = Integer::parseInt;
Integer result = f.apply("123");

Integer 클래스의 정적 메서드인 parseInt()를 메서드 레퍼런스로 전달했다. 해당 구현체 소스에서 사용할 수 있는 지역변수는 String 타입의 str밖에 없다. 그리고 리턴타입은 제네릭을 이용해 Integer를 건네줘야하는걸 추론할 수 있다. 때문에 메서드만 전달해주면 컴파일러가 알아서 호출해주는것이다. 지금은 예제코드라 이걸 왜 쓰는지 이해가 안갈수도있지만 복잡한 코드에서 어떤 메서드를 사용했는지 선언적으로 코딩을 할 수 있도록 해준다.


Function<String, Boolean> f = String::isEmpty;
Boolean result = f.apply("123");

isEmpty() 메서드는 정적메서드는 아니다. 하지만 인자로 넘어오는 타입이 String 이기때문에 해당 타입의 메서드를 호출해준다.


메서드 레퍼런스를 사용할 수 있는 경우에 따라 사용법이나 추론법이 약간 다르다.


1) 정적 메서드 레퍼런스

위에서 본 예제이다. 호출하고자하는 정적 클래스::메서드명 형식으로 작성한다.

Function<String, Integer> f = Integer::parseInt;
Integer result = f.apply("123");


2) 인자 인스턴스 메서드 레퍼런스

이것 역시 위에서 본 예제이다. 인자의 타입이 명확할때 해당 타입 클래스::메서드명 형식으로 작성한다.

Function<String, Boolean> f = String::isEmpty;
Boolean result = f.apply("123");

3) 생성자 메서드 레퍼런스
Supplier<String> s = String::new;
생성자도 메서드 레퍼런스로 표현할 수 있다.
Supplier는 인자없이 리턴만 하는 메서드를 갖고있다. 메서드를 호출하면 new로 생성된 String 객체를 리턴한다. 물론 기본생성자를 호출하게 된다.

4) 바깥 인스턴스 메서드 레퍼런스

람다 캡쳐링(Lambda Capturing)을 이용해 람다 표현식 바깥에있는 인스턴스의 메서드를 호출할때 사용한다. 인자 인스턴스와는 분명히 다르다. 코드를 보고 이해하자.

String str = "hello";
Predicate<String> p = str::equals;
p.test("world");


equals() 메서드는 호출하는 객체와 인자로 넘기는 객체. 총 2개의 객체가 필요한 메서드이다. Predicate를 구현할때 인자로 넘어오는 객체는 1개뿐이기때문에 

Predicate<String> p = String::equals;

이런 형태로는 작성할 수 없다. 인자로 넘어오는 String 객체의 equals() 메서드를 호출해버리면 인자로 보낼 객체를 추론할 수 없기때문이다.

바깥에 있는 str의 equals를 호출해야 인자로 넘어오는 String 객체를 equals() 메서드의 인자로 보내겠다는 의도를 추론할 수 있다.


Comparator<String> c = String::compareTo;

이런식의 작성도 할 수 있다. compareTo() 메서드를 호출하기위해선 호출하는 객체와 인자로 보내는 객체, 객체 2개가 필요한데 compare() 메서드에는 마침 2개의 인자가 넘어온다. 첫번째인자로 호출하고 두번째인자를 인자로 보내는 것이다.


예제코드만 봤을땐 특별히 어려울게 없을것같지만 필요할때 사용한다거나 남이 짠코드를 파악할때 메서드 레퍼런스를 만나면 의외로 헷갈릴일이 많을것이다. 이런일이 없게하려면 람다를 메서드 레퍼런스로, 메서드 레퍼런스를 람다로 바꾸는 연습을 많이 해보길 바란다.

댓글
  • 프로필사진 지나가는 나그네 바깥 인스턴스 메서드 레퍼런스에 대해 질문있습니다. method 영역 외부의 변수를 참조하는 것처럼 보이는데
    실제로는 선언 시점에서 해당 ``str`` 값을 복사해서 내부에 놓는 것일까요?
    2018.06.19 12:09
  • 프로필사진 LichKing 실제 바이너리단의 구현은 https://d2.naver.com/helloworld/4911107 이글을 참고하면 좋지않을까 싶습니다.
    다만 그정도까지의 세부구현은 나중에 자바버전이 올라가면서 바뀔수도있을것 같네요.
    2018.07.01 14:20 신고
댓글쓰기 폼