Java 8에서 람다 구문 대신 메소드 참조 구문을 사용하면 성능 이점이 있습니까?


56

메소드 참조가 람다 랩퍼의 오버 헤드를 생략합니까? 그들은 미래에 있을까요?

메소드 참조에 대한 Java 학습서에 따르면 :

때로는 ... 람다 식은 기존 메소드를 호출하는 것 외에는 아무것도하지 않습니다. 이 경우 기존 방법을 이름으로 참조하는 것이 더 명확합니다. 메소드 참조를 통해이를 수행 할 수 있습니다. 그것들은 이미 이름을 가진 메소드에 대한 콤팩트하고 읽기 쉬운 람다 식입니다.

몇 가지 이유로 메서드 참조 구문보다 람다 구문을 선호합니다.

람다는 더 명확하다

오라클의 주장에도 불구하고 메소드 참조 구문이 모호하기 때문에 객체 메소드 참조보다 람다 구문을 쉽게 읽을 수 있습니다.

Bar::foo

x 클래스에서 정적 단일 인수 메소드를 호출하고 x를 전달하고 있습니까?

x -> Bar.foo(x)

아니면 x에서 인수가없는 인스턴스 메소드를 호출하고 있습니까?

x -> x.foo()

메소드 참조 구문은 둘 중 하나에 해당 될 수 있습니다. 실제로 코드가 수행하는 작업을 숨 깁니다.

람다는 더 안전하다

Bar :: foo를 클래스 메서드로 참조하고 나중에 Bar가 동일한 이름 (또는 그 반대)의 인스턴스 메서드를 추가하면 코드가 더 이상 컴파일되지 않습니다.

람다를 일관되게 사용할 수 있습니다

모든 함수를 람다로 감쌀 수 있으므로 어디에서나 동일한 구문을 일관되게 사용할 수 있습니다. 메소드 참조 구문은 기본 배열을 가져 오거나 리턴하거나, 확인 된 예외를 발생 시키거나 인스턴스 및 정적 메소드와 동일한 메소드 이름을 사용하는 메소드에서 작동하지 않습니다 (메소드 참조 구문이 호출되는 메소드에 대해 모호하기 때문에) . 동일한 수의 인수로 메소드를 오버로드하면 작동하지 않지만 어쨌든 그렇게하지 않아야합니다 (Josh Bloch의 항목 41 참조).

결론

그렇게 할 때 성능 저하가 없다면 IDE에서 경고를 끄고 가끔 메서드 참조를 코드에 뿌리지 않고 람다 구문을 일관되게 사용하고 싶습니다.

추신

여기나 거기에 있지 않지만 꿈에서 객체 메소드 참조는 다음과 같이 보이고 람다 래퍼없이 객체에서 직접 메소드에 대해 invoke-dynamic을 적용합니다.

_.foo()

2
이것은 귀하의 질문에 대답하지 않지만 클로저를 사용하는 다른 이유가 있습니다. 내 의견으로는, 그들 중 많은 사람들이 추가 함수 호출의 작은 오버 헤드보다 훨씬 큽니다.

9
나는 당신이 조기에 성능에 대해 걱정하고 있다고 생각합니다. 제 생각에는 성능은 메소드 참조를 사용하기위한 2 차 고려 사항이어야합니다. 그것은 당신의 의도를 표현하는 것입니다. 어딘가에 함수를 전달해야한다면, 그 함수를 위임하는 다른 함수 대신 함수전달하면 어떨까요? 나에게는 이상해 보인다. 함수가 일류라고 생각하지 않는 것처럼, 함수를 이동하려면 익명의 보호자가 필요합니다. 그러나 귀하의 질문을보다 직접적으로 다루기 위해 메소드 참조가 정적 호출로 구현 될 수 있는지 놀라지 않을 것입니다.
Doval

7
.map(_.acceptValue()): 내가 실수하지 않으면 이것은 스칼라 구문과 매우 비슷합니다. 어쩌면 당신은 잘못된 언어를 사용하려고했을 것입니다.
Giorgio

2
"메소드 참조 구문은 void를 리턴하는 메소드에서 작동하지 않습니다" -어떻게? 나는 통과하고있어 System.out::printlnforEach()... 모든 시간?
Lukas Eder

3
"메소드 참조 구문은 프리미티브 배열을 가져 오거나 리턴하는 메소드에서 작동하지 않습니다. 확인 된 예외를 처리하십시오 ..." 맞지 않습니다. 해당 기능 인터페이스가 허용하는 한 메소드 참조와 람다 표현식을 사용할 수 있습니다.
스튜어트 마크

답변:


12

많은 시나리오에서 람다와 메소드 참조가 동일하다고 생각합니다. 그러나 람다는 선언 인터페이스 유형으로 호출 대상을 래핑합니다.

예를 들어

public class InvokeTest {

    private static void invoke(final Runnable r) {
        r.run();
    }

    private static void target() {
        new Exception().printStackTrace();
    }

    @Test
    public void lambda() throws Exception {
        invoke(() -> target());
    }

    @Test
    public void methodReference() throws Exception {
        invoke(InvokeTest::target);
    }
}

콘솔 출력에 stacktrace가 표시됩니다.

에서 lambda(), 메소드 호출이 target()있다 lambda$lambda$0(InvokeTest.java:20), 추적 라인 정보를 가지고있다. 분명히, 그것은 당신이 쓰는 람다입니다. 컴파일러는 익명의 메소드를 생성합니다. 그런 다음 람다 메소드의 호출자는 JVM InvokeTest$$Lambda$2/1617791695.run(Unknown Source)에서 invokedynamic호출 하는 것과 같습니다 . 호출이 생성 된 메소드에 링크되어 있음을 의미합니다 .

에서는 methodReference(), 메소드 호출은 target()직접적이며 InvokeTest$$Lambda$1/758529971.run(Unknown Source), 상기 통화 수단은 직접 연결되는 InvokeTest::target방법 .

결론

무엇보다도, 람다 식을 사용하면 메소드 참조와 비교할 때 람다 에서 생성 메소드에 대한 메소드 호출 이 하나 더 발생합니다.


1
메타 팩토리에 대한 아래의 답변이 약간 낫습니다. 관련된 유용한 주석을 추가했습니다 (즉, Oracle / OpenJDK와 같은 구현이 ASM을 사용하여 람다 / 메소드 핸들 인스턴스를 만드는 데 필요한 래퍼 클래스 객체를 생성하는 방법)
Ajax

29

그것은 메타 팩토리 에 관한 것 입니다

첫째, 대부분의 방법 참조는 람다 메타 팩토리에 의한 탈당을 필요로하지 않으며, 단순히 참조 방법으로 사용됩니다. Lambda 식 번역 ( "TLE") 기사의 "Lambda body sugaring"섹션에서 :

모든 것이 동일하고, 개인 메소드는 비 개인 메소드보다 선호되고 정적 메소드는 인스턴스 메소드보다 선호됩니다. 람다 표현이 나타나는 가장 안쪽 클래스에서 람다 본문의 설탕이 제거되는 것이 가장 좋습니다. 서명은 람다의 본문 서명과 일치해야합니다. 인수는 캡처 된 값에 대해 인수 목록 앞에 추가해야하며 메소드 참조를 완전히 제거하지 않아야합니다. 그러나이 기본 전략에서 벗어나야 할 경우가 있습니다.

이것은 TLE의 "The Lambda Metafactory"에서 더욱 강조됩니다.

metaFactory(MethodHandles.Lookup caller, // provided by VM
            String invokedName,          // provided by VM
            MethodType invokedType,      // provided by VM
            MethodHandle descriptor,     // lambda descriptor
            MethodHandle impl)           // lambda body

impl인자 람다 방법, desugared 람다 본체 또는 방법 참조라는 방법 중 하나를 식별한다.

정적 ( Integer::sum) 또는 제한되지 않은 인스턴스 메소드 ( Integer::intValue) 참조는 desugaring 없이 '빠른 경로' 메타 팩토리 변형에 의해 최적으로 처리 될 수 있다는 점에서 '가장 단순하거나 가장 편리한' 참조 입니다. 이 장점은 TLE의 "계산 변형"에서 유용하게 지적됩니다.

필요하지 않은 인수를 제거하면 클래스 파일이 더 작아집니다. 또한 빠른 경로 옵션을 사용하면 VM이 람다 변환 작업을 강화하여 "복싱"작업으로 처리하고 Unbox 최적화를 촉진 할 수 있습니다.

당연히, 인스턴스 캡처 메소드 참조 ( obj::myMethod)는 호출 된 메소드 핸들에 대한 인수로 바운딩 된 인스턴스를 제공해야합니다. 이는 '브리지'메소드를 사용하여 디 슈잉 (desugaring)이 필요할 수 있습니다.

결론

나는 당신이 암시하고있는 람다 '래퍼'가 무엇인지 확실하지 않지만, 사용자 정의 람다 또는 메소드 참조를 사용한 최종 결과는 동일하지만 도달 하는 방법 은 상당히 다른 것처럼 보입니다. 지금이 아니라면 미래에 다를 수 있습니다. 따라서 메소드 참조가 메타 팩토리에 의해보다 최적의 방식으로 처리 될 수 있다고 생각합니다.


1
이것은 람다를 만드는 데 필요한 내부 기계를 실제로 다루기 때문에 가장 관련성 높은 답변입니다. 실제로 람다 생성 프로세스를 디버깅 한 사람 (주어진 람다 / 메소드 참조에 대해 안정적인 ID를 생성하여 맵에서 제거 할 수있는 방법을 알아 내기 위해)을 생성 한 사람은 람다의 인스턴스. Oracle / OpenJDK는 ASM을 사용하여 필요한 경우 람다에서 참조되는 변수 (또는 메소드 참조의 인스턴스 규정 자)를 닫는 클래스를 동적으로 생성합니다.
Ajax

1
... 변수를 닫는 것 외에도, 람다는 특히 선언 클래스에 주입되는 정적 메소드를 작성하므로 작성된 람다는 호출하려는 함수에 맵핑하기 위해 호출 할 것이 있습니다. 인스턴스 한정자를 사용하는 메서드 참조는 해당 인스턴스를이 메서드의 매개 변수로 사용합니다. 개인 클래스의 this :: method 참조 가이 클로저를 건너 뛰는 지 테스트하지는 않았지만 정적 메소드가 실제로 중간 메소드를 건너 뛰고 테스트 대상에서 호출 대상을 준수하도록 객체를 작성하는지 테스트했습니다. 호출 사이트).
Ajax

1
아마도 프라이빗 메소드에는 가상 디스패치가 필요하지 않지만, 더 많은 퍼블릭 객체는 생성 된 정적 메소드를 통해 인스턴스를 닫아야하므로 실제 인스턴스에서 가상 머신을 호출하여 재정의가 발생했는지 확인할 수 있습니다. TL; DR : 메소드 참조는 인수가 적을수록 닫히므로 누설이 적고 동적 코드 생성이 덜 필요합니다.
Ajax

3
마지막 스팸성 댓글 ... 역동적 인 클래스 생성은 꽤 무겁습니다. 클래스 로더에 익명 클래스를로드하는 것보다 여전히 낫지 만 (가장 무거운 부분은 한 번만 수행되므로) 실제로 람다 인스턴스를 만드는 데 얼마나 많은 작업이 필요한지 놀라게 될 것입니다. 람다 대 메소드 참조 대 익명 클래스의 실제 벤치 마크를 보는 것이 흥미로울 것입니다. 동일한 것을 참조하지만 서로 다른 위치에서 두 개의 람다 또는 메서드 참조는 런타임에 (같은 메서드 내에서, 서로 바로 뒤에) 다른 클래스를 만듭니다.
Ajax


0

람다 식을 사용할 때 성능에 영향을 줄 수있는 심각한 결과가 있습니다.

람다 식을 선언 하면 로컬 범위에 대한 클로저 가 생성 됩니다.

이것이 무엇을 의미하며 성능에 어떤 영향을 미칩니 까?

당신이 물어봐서 기뻐요 이것은 작은 람다 식 각각이 작은 익명의 내부 클래스라는 것을 의미하며 람다 식의 동일한 범위에있는 모든 변수에 대한 참조를 포함합니다.

이는 this객체 인스턴스와 모든 해당 필드에 대한 참조 도 의미 합니다. 람다의 실제 호출 타이밍에 따라 가비지 수집기가 람다를 보유한 객체가 여전히 살아있는 한 가비지 수집기가 이러한 참조를 놓을 수 없으므로 상당한 리소스 누출이 발생할 수 있습니다.


비 정적 메서드 참조도 마찬가지입니다.
Ajax

12
자바 람다는 엄격히 폐쇄 된 것은 아니다. 익명의 내부 클래스도 아닙니다. 선언 된 범위 내 모든 변수에 대한 참조는 없습니다. 그들은 실제로 참조하는 변수에 대한 참조만을 가지고 있습니다. 이것은 참조 될 수 있는 this객체 에도 적용됩니다 . 따라서 람다는 단순히 범위를 지정하여 리소스를 유출하지 않습니다. 필요한 오브젝트 만 보유합니다. 반면에 익명의 내부 클래스는 리소스를 유출 할 수 있지만 항상 그렇지는 않습니다. 예제는 다음 코드를 참조하십시오. a.blmq.us/2mmrL6v
squid314

그 대답은 간단합니다
Stefan Reich

@StefanReich 감사합니다. 매우 도움이되었습니다!
Roland Tepp
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.