Lambda 表达式与方法参考实现细节

Lambda expression vs method reference implementation details

鉴于此:

class MyClass {
    static class A {
        public boolean property() {
            return Math.random() < 0.5;
        }
    }

    static List<A> filterLambda(List<A> list) {
        return list.stream().filter(a -> a.property()).collect(Collectors.toList());
    }

    static List<A> filterMethodCall(List<A> list) {
        return list.stream().filter(A::property).collect(Collectors.toList());
    }
}

PD:我知道这个问题与 this one 类似,但我认为它没有得到正确解决。

Here 是方法引用的 java 语言规范。

Here 是 lambda 表达式的规范。

从规则的角度来看,Lambda 要复杂得多。

然而,在这两种情况下,结果都是 invokedynamic 调用。

Brian Goetz 写了一篇 doc 介绍其工作原理的文章。

这是 Brett Oken 链接的 Brian Goetz's doc 的摘录:

When the compiler encounters a lambda expression, it first lowers (desugars) the lambda body into a method whose argument list and return type match that of the lambda expression, possibly with some additional arguments (for values captured from the lexical scope, if any.) At the point at which the lambda expression would be captured, it generates an invokedynamic call site, which, when invoked, returns an instance of the functional interface to which the lambda is being converted. This call site is called the lambda factory for a given lambda. The dynamic arguments to the lambda factory are the values captured from the lexical scope. The bootstrap method of the lambda factory is a standardized method in the Java language runtime library, called the lambda metafactory. The static bootstrap arguments capture information known about the lambda at compile time (the functional interface to which it will be converted, a method handle for the desugared lambda body, information about whether the SAM type is serializable, etc.)

Method references are treated the same way as lambda expressions, except that most method references do not need to be desugared into a new method; we can simply load a constant method handle for the referenced method and pass that to the metafactory.

从同一文档中提取的示例:

例如,考虑一个捕获字段 minSize 的 lambda:

list.filter(e -> e.getSize() < minSize )

我们将其脱糖为实例方法,并将接收者作为第一个捕获的参数传递:

list.forEach(INDY((MH(metaFactory), MH(invokeVirtual Predicate.apply),
                    MH(invokeVirtual B.lambda))( this ))));

private boolean lambda(Element e) {
    return e.getSize() < minSize; }

同时

list.filter(String::isEmpty)

译为:

list.filter(indy(MH(metaFactory), MH(invokeVirtual Predicate.apply),
             MH(invokeVirtual String.isEmpty))()))

我们可以找到差异的 lambda 表达式和方法参考的一个案例是用作供应商接口的一部分。假设我们有下面的静态方法

public static <T, E> T catchException(Supplier<T> resolver) {
    try {
        T result = resolver.get();
        return  result ;
    } catch (Exception e) {
        System.out.println("Exception");
        return null;
    }
}

当我们如下调用 lambda 表达式的方法时,代码工作正常,因为 lambda 表达式作为 Supplier 的一部分传递并且仅在捕获异常的地方调用 get() 方法时执行。

List<String> underlyers=null;
System.out.println(catchException(()->underlyers.size()));

当我们使用方法引用调用方法时,我们在调用方法之前得到 NullPointerExecption,因为引用是在传递给供应商之前执行的。在这种情况下,控件未到达 get() 方法。

List<String> underlyers=null;
System.out.println(catchException(underlyers::size));