Java 8 lambda是作为内部类、方法或其他东西编译的吗



我今天读了这篇关于lambdas:的文章

http://www.infoq.com/articles/Java-8-Lambdas-A-Peek-Under-the-Hood

这篇文章建议,lambdas不是作为一个内部类实现的(由于性能原因)。它给出了一个例子,说明lambda表达式可以编译为类的(静态)方法。

我尝试了一个非常简单的片段:

private void run() {
    System.out.println(this);
    giveHello(System.out::println);
}
private void giveHello(Consumer<String> consumer) {
    System.out.println(consumer);
    consumer.accept("hello");
}

输出为:

sample.Main@14ae5a5
sample.Main$$Lambda$1/168423058@4a574795
hello

所以这不是同一个例子。这也不是某个中心的"Lambda工厂"的例子。。

那么lambdas是如何实现的呢?

假设您传递的是实际lambda表达式而不是方法引用,则表达式本身将作为单独的合成方法进行编译。除了预期功能接口的任何形式参数(例如,在Consumer<String>的情况下为单个String)之外,它还将包括任何捕获值的参数。

在lambda表达式或方法引用出现的代码位置,会发出invokedynamic指令。第一次命中此指令时,会调用LambdaMetafactory上的引导方法。这个引导方法将修复委托给目标方法的目标函数接口的实际实现,这就是返回的内容。目标方法要么是表示lambda体的合成方法,要么是使用::运算符提供的任何命名方法。当实现函数接口的类被创建时,该过程被推迟;它不会在编译时发生。

最后,运行时用引导结果1修补invokedynamic站点,这实际上是对生成的委托的构造函数调用,其中传递了任何捕获的值,包括(可能)调用目标2。这通过删除后续调用的引导进程来减轻性能打击。


1请参阅java.lang.invoke"链接的定时"一章的末尾,由@Holger提供。

2在lambda没有捕获的情况下,invokedynamic指令通常会解析为一个共享委托实例,该实例可以在随后的调用中重用,尽管这是一个实现细节。

我问了自己同样的问题,找到了这段视频,这是Brian Goetz的演讲。介绍如何在java中实现lambda是非常有用的。

编辑(摘要):看了一会儿,所以这可能不是完全正确的。编译文件时,编译器会留下lambda应该做什么的描述。然后,JRE在运行代码时,将决定如何实现lambda。有几个选项,内联,方法引用,匿名类。然后,它将使用动态分配来分配实现。

Lambda在Java 8中被称为函数接口,即具有一个default方法的匿名接口。

我不确定,但我相信,它只是编译到Anonymous内部类。Consumer<T>是一个接口,所以我想说你的例子几乎等于

    giveHello(new Consumer<String>() {
        @Override
        public void accept(String t) {
            System.out.println(t);
        }
    });

编辑

经过研究,上述答案并不完整和有效。Lambda表达式可能会被翻译成匿名内部类,但不必(通常也不会)。

相关内容

最新更新