【问题标题】:Lambda expression vs method reference implementation detailsLambda 表达式 vs 方法参考实现细节
【发布时间】:2018-11-05 07:58:07
【问题描述】:

鉴于此:

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,但我认为它没有得到正确解决。

【问题讨论】:

  • “编译器做什么”可能超出了这里的答案范围。该规范提供了一些理论背景信息,但不清楚您想要深入了解实际编译器实现。我只能说为这两种方法生成的字节码是相同的

标签: java lambda


【解决方案1】:

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

当编译器遇到 lambda 表达式时,它首先降低 (desugars) 将 lambda 主体转换为一个方法,其参数列表和 返回类型与 lambda 表达式匹配,可能有一些 附加参数(对于从词法范围捕获的值,如果 任何。)在捕获 lambda 表达式的位置, 它生成一个 invokedynamic 调用站点,当调用它时,返回 lambda 所在的功能接口的一个实例 转换。此调用站点称为给定的 lambda 工厂 拉姆达。 lambda 工厂的动态参数是值 从词法范围中捕获。 lambda 的引导方法 factory 是 Java 语言运行时库中的一种标准化方法, 称为 lambda 元工厂。静态引导参数捕获 编译时已知的有关 lambda 的信息(函数 它将被转换到的接口,一个方法句柄 脱糖 lambda 主体,有关 SAM 类型是否为 可序列化等)

方法引用的处理方式与 lambda 表达式相同, 除了大多数方法引用不需要脱糖成 新方法;我们可以简单地为 引用的方法并将其传递给元工厂。

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

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

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

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

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

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

虽然

list.filter(String::isEmpty)

翻译为:

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

【讨论】:

    【解决方案2】:

    我们可以找到差异 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));
    

    【讨论】:

      【解决方案3】:

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

      Here 是 lambda 表达式的规范。

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

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

      Brian Goetz 写了一封 doc 来说明这是如何工作的。

      【讨论】:

      • 我不是在问应该使用哪一个,但感谢您提供的链接
      猜你喜欢
      • 2023-04-05
      • 1970-01-01
      • 2015-06-23
      • 2010-10-25
      • 2014-10-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-03-08
      相关资源
      最近更新 更多