Do method references skip the overhead of the lambda wrapper? Might they in the future?

According to the Java Tutorial on Method References:

Sometimes... a lambda expression does nothing but call an existing method. In those cases, it's often clearer to refer to the existing method by name. Method references enable you to do this; they are compact, easy-to-read lambda expressions for methods that already have a name.

I prefer the lambda syntax to the method reference syntax for several reasons:

Lambdas are clearer

Despite Oracle's claims, I find the lambda syntax easier-to-read than the object method reference short-hand because the method reference syntax is ambiguous:

Bar::foo

Are you calling a static one-argument method on the class of x and passing it x?

x -> Bar.foo(x)

Or are you calling a zero-argument instance method on x?

x -> x.foo()

The method reference syntax could stand in for either one. It hides what your code is actually doing.

Lambdas are safer

If you reference Bar::foo as a class method and Bar later adds an instance method of the same name (or vice-versa), your code will no longer compile.

You can use lambdas consistently

You can wrap any function in a lambda - so you can use the same syntax consistently everywhere. The method reference syntax won't work on methods that take or return primitive arrays, throw checked exceptions, or have the same method name used as an instance and a static method (because the method reference syntax is ambiguous about which method would be called). They don't work when you have overloaded methods with the same number of arguments, but you shouldn't do that anyway (see Josh Bloch's item 41) so we can't hold that against method references.

Conclusion

If there's no performance penalty for doing so, I'm tempted to turn off the warning in my IDE and use the lambda syntax consistently without sprinkling the occasional method reference into my code.

P.S.

Neither here nor there, but in my dreams, object method references look more like this and apply invoke-dynamic against the method directly on the object without a lambda wrapper:

_.foo()
有帮助吗?

解决方案

In many scenarios, I think lambda and method-reference is equivalent. But the lambda will wrap the invocation target by the declaring interface type.

For example

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);
    }
}

You will see the console output the stacktrace.

In lambda(), the method calling target() is lambda$lambda$0(InvokeTest.java:20), which has traceable line info. Obviously, that is the lambda you write, the compiler generates an anonymous method for you. And then, the caller of the of the lambda method is something like InvokeTest$$Lambda$2/1617791695.run(Unknown Source), that is the invokedynamic call in JVM, it means the call is linked to the generated method.

In methodReference(), the method calling target() is directly the InvokeTest$$Lambda$1/758529971.run(Unknown Source), it means the call is directly linked to the InvokeTest::target method.

Conclusion

Above all, compare to method-reference, using lambda expression will only cause one more method call to the generating method from lambda.

其他提示

It's all about the metafactory

First, most method references do not need desugaring by the lambda metafactory, they are simply used as the reference method. Under the section "Lambda body sugaring" of the Translation of Lambda Expressions ("TLE") article:

All things being equal, private methods are preferable to nonprivate, static methods preferable to instance methods, it is best if lambda bodies are desugared into in the innermost class in which the lambda expression appears, signatures should match the body signature of the lambda, extra arguments should be prepended on the front of the argument list for captured values, and would not desugar method references at all. However, there are exception cases where we may have to deviate from this baseline strategy.

This is further highlighted further down in TLE's "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

The impl argument identifies the lambda method, either a desugared lambda body or the method named in a method reference.

A static (Integer::sum) or unbounded instance method (Integer::intValue) references are the 'simplest' or the most 'convenient', in the sense that they can be optimally handled by a 'fast-path' metafactory variant without the desugaring. This advantage is helpfully pointed out in TLE's "Metafactory variants":

By eliminating arguments where they are not needed, classfiles become smaller. And the fast path option lowers the bar for the VM to intrinsify the lambda conversion operation, enabling it to be treated as a "boxing" operation and faciliating unbox optimizations.

Naturally, an instance-capturing method reference (obj::myMethod) needs to provide the bounded instance as an argument to the method handle for invocation, which may mean the need of desugaring using 'bridge' methods.

Conclusion

I'm not exactly sure what is the lambda 'wrapper' you are hinting at, but even though the ultimate result of using your user-defined lambdas or method references are the same, the way that is reached seems to be quite different, and can be different in the future if that's not the case now. Hence, I suppose it's more likely than not that method references can be handled in a more optimal way by the metafactory.

There is one quite serious consequence when using lambda expressions that may affect performance.

When you declare a lambda expression, you are creating a closure over the local scope.

What does this mean and how does it affect performance?

Well I'm glad you asked. It means that each of those little lambda expressions is a small anonymous inner class and that means it carries with it a reference to all variables that are in the same scope of the lambda expression.

This means also this reference of the object instance and all of its fields. Depending on the timing of the actual invocation of the lambda, this can amount to a pretty significant resource leak as the garbage collector is unable to let go of these references as long as the object holding to a lambda is still alive...

许可以下: CC-BY-SA归因
scroll top