티스토리 뷰

사람들은 참 게으르다. 람다식으로 소스 코드를 상당히 간소화시켰지만 이를 더 줄이려 한다. 그 방법이 메소드 참조이다.

// map.merge를 이용해 구현한 빈도표 - 람다 방식과 메서드 참조 방식을 비교해보자. (259쪽)
public class Freq {
    public static void main(String[] args) {
        Map<String, Integer> frequencyTable = new TreeMap<>();
        
        for (String s : args)
            frequencyTable.merge(s, 1, (count, incr) -> count + incr); // 람다
        System.out.println(frequencyTable);
    }
}

Map<K,V> map.merge()

remappingFuction은 BiFunction의 구현체이다. BiFunction은 그냥 T랑 U 가지고 함수를 진행하고 R을 반환한다. merge 함수에서는 이를 Map의 Value로 인스턴스를 통일했다.

다시 이 merge 함수를 사용하는 main문으로 돌아가서 함수를 이해하면, key가 존재하지 않으면 value를 1로 세팅해서 put 하고 존재한다면 기존 값에 1을 증가시켜서 put 하는 코드이다. 즉 해당 key가 몇 개 들어가 있는지를 저장하는 자료구조의 구현이라고 볼 수 있다.

 

람다식이 많은 소스 코드를 감소시켜줬지만 count와 incr도 줄이고 싶다. 아래의 코드와 같이 메소드 참조를 사용해 간소화할 수 있다.

for (String s : args)
    frequencyTable.merge(s, 1, Integer::sum); // 메서드 참조
System.out.println(frequencyTable);

두 개를 더해서 그 값을 반환하는 메소드를 Integer의 static method인 sum을 통해 구현했다. 훨씬 간편하고 보기 좋다. 이렇게 메소드 참조를 사용하면 람다식이 할 수 없는 문서화를 적용할 수 있다. 코드를 들어가서 읽거나 javadoc의 문서를 보며 어떻게 동작하는지 확인할 수 있다.

 

 

그렇다면 모든 경우에서 람다를 메소드 참조로 대체해야 할까? 다음 경우를 살펴보자

service.excute(KingGodEmperalDeveloperJongSeok::action);

service.excute(() -> action());

엄청나게 긴 클래스의 메소드를 참조한 경우이다. 아래는 이를 람다로 대치한 코드이다. 메소드 참조의 경우가 훨씬 간결하지도, 가독성이 좋지도 않다.

추후에 다루겠지만 메소드 참조와 람다의 구현방식은 똑같기 때문에 성능적 차이는 없다. 둘 중 가독성이 더욱 좋다고 생각되는 것을 취사 선택하면 된다.

 

 

 

메소드 참조 유형

메소드 참조에 사용될 수 있는 유형은 아래의 다섯 가지이다.

  • static method 참조
Integer::parseInt // 메소드
str -> Integer.parseInt(str) // 람다

어떤 클래스의 static method를 사용하는 경우이다. 

 

  • 한정적(인스턴스)
Instance.now()::isAfter // 메소드

Instant then = Instant.now(); // 람다
t -> then.isAfter(t)


public static void main(String[] args) {
    printFunctionalInterface(new DoubleFuntion()::zzz);


    DoubleFuntion doubleFuntion = new DoubleFuntion();
    printFunctionalInterface(doubleFuntion::zzz);
}

private static class DoubleFuntion{
    public Double zzz(Double left, Double right){
        return left + right;
    }
}

 

어떤 특정한 인스턴스를 정해두고 해당 인스턴스의 메소드를 호출하는 방식이다. Instant.now()처럼 static factory 패턴을 통해 인스턴스를 반환할 수도 있고 아래의 main 문처럼 객체를 생성하고 메소드 호출, 생성된 객체의 이름으로 메소드를 호출하는 것도 가능하다.

 

  • 비한정적(인스턴스)
String::toLowerCase // 메소드
str -> str.toLowerCase() // 람다

어떤 인스턴스가 사용되는지 정해져 있지 않은 경우이다. 예시로 String이 담겨있는 List의 stream을 순회하며 리스트 내부의 문자를 전부 소문자로 변환하는 코드를 상상할 수 있다. 이 경우에는 List 내부의 String 인스턴스 하나하나를 순회하며 toLowerCase를 실행해 인스턴스가 비한정적이다.

 

  • 클래스 생성자
TreeMap<K,V>::new // 메소드
() -> new TreeMap<K,V>() // 람다

public static void constructorTest(Function<Double, DoubleFuntion> f){

}

public static void main(String[] args) {
    printFunctionalInterface(new DoubleFuntion()::zzz);


    DoubleFuntion doubleFuntion = new DoubleFuntion();
    printFunctionalInterface(doubleFuntion::zzz);

    constructorTest(DoubleFuntion::new);
}

private static class DoubleFuntion{
    public DoubleFuntion(){

    }
    public DoubleFuntion(Double aa){

    }
}

생성자를 통해 인스턴스를 받아오는 메소드 참조이다. 생성자가 여러 개인 경우엔 메소드가 인자로 받는 Functional Interface를 바꾸면 자동으로 타입 추론이 된다. constructorTest의 파라미터를 Function<T, R>로 지정하면 생성자의 파라미터인 T를 통해 인스턴스 R을 생성할 수 있다.

 

  • 배열 생성자
int[]::new // 메소드
len -> new int[len] // 람다

배열을 생성할 수도 있다.

 

 

 

// 추가

자바에는 함수 객체가 없어 함수 객체를 흉내내기 위해 람다 인스턴스를 생성하는 방식을 아이템 42를 통해 확인했다. 그렇다면 메소드 참조는 어떤 방식으로 구현돼 있을까? 궁금해서 인스턴스를 찍어봤다.

public class TestClass {

    public static void printFunctionalInterface(DoubleBinaryOperator hi){
        System.out.println("인스턴스 : " + hi);
        // 인스턴스 : tt.TestClass$$Lambda$14/0x0000000800066840@3cda1055
    }

    public static void main(String[] args) {
        printFunctionalInterface(TestClass::hi);
    }

    private static Double hi(Double a, Double b){
        return a + b;
    }
}

메소드 참도도 결국 람다를 통해 구현돼 있음을 확인 할 수 있다.

TestClass의 hi 메소드를 복사해 static 메소드로 생성하고 뭐 주변에서 캡쳐할꺼 있으면 캡쳐해서 가져오고,, 람다 객체를 생성해 아까 생성한 static 메소드를 호출하는 방식이다. 문법적으로 지원하는 방식이 있다면 hi 메소드의 파라미터와 리턴 타입을 통해 타입 추론을 하고 어떤 메소드를 복사할지 결정하는 부분이다.

 

 

결론

람다와 메소드 참조 모두 람다 인스턴스 생성 방식으로 구현된다. 둘 중에 가독성더 높은 것을 채택하자.

댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
TAG
more
«   2024/09   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30
글 보관함