Java 8 lambdas是作为内部类,方法还是其他东西编译的?

Yan*_*nko 16 java lambda java-8

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

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

文章建议,lambda 不是 作为anon内部类实现的(由于性能).它给出了一个示例,可以将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");
}
Run Code Online (Sandbox Code Playgroud)

输出是:

sample.Main@14ae5a5
sample.Main$$Lambda$1/168423058@4a574795
hello
Run Code Online (Sandbox Code Playgroud)

所以它不是同一个实例.它不是一些中央的"Lambda Factory"实例.

那么lambdas是如何实现的?

Mik*_*bel 10

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

在出现lambda表达式或方法引用的代码位置,将invokedynamic发出指令.第一次触发该指令时,将调用一个bootstrap方法LambdaMetafactory.这个引导方法将修复目标功能接口的实际实现,该接口委托给目标方法,这就是返回的内容.目标方法是表示lambda主体的合成方法或使用::运算符提供的任何命名方法.而实现该功能的接口的类创建,则处理被推迟; 它不会在编译时发生.

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


1请参阅java.lang.invoke章节"联系时间"的结尾,由@Holger提供.

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

  • 保证*`invokedynamic`使用第一次引导的结果.参见[`java.lang.invoke`章节末尾"连接时间"](http://docs.oracle.com/javase/7/docs/api/java/lang/invoke/package-summary.html#indyinsn ):"*每个动态调用站点在第一次调用之前,最多从未链接到链接转换一次.无法撤消已完成的bootstrap方法调用的效果.*" (2认同)
  • 作为附录,如果lambda表达式没有捕获的值,则`invokedynamic`指令通常会被解析为返回常量委托对象的方法句柄,因此它始终是相同的.但是**依赖于实现. (2认同)
  • 是的,正在运行时生成一个类; 您可以从调试输出中看到合成类的名称为"Main $$ Lambda $ 1".是否在每次调用时创建一个新实例是一个实现细节,并取决于lambda是否捕获任何非常量值(参见脚注#2).如果有捕获的值,则每次到达呼叫站点时都会创建一个新实例; 如果没有,您可能最终使用共享实例. (2认同)