lambda表达式是否在每次执行时都在堆上创建一个对象?

Bas*_*igt 166 java lambda java-8

当我使用Java 8的新语法糖迭代一个集合时,例如

myStream.forEach(item -> {
  // do something useful
});
Run Code Online (Sandbox Code Playgroud)

这不等同于下面的"旧语法"片段吗?

myStream.forEach(new Consumer<Item>() {
  @Override
  public void accept(Item item) {
    // do something useful
  }
});
Run Code Online (Sandbox Code Playgroud)

这是否意味着Consumer每次迭代集合时都会在堆上创建一个新的匿名对象?这需要多少堆空间?它有什么性能影响?这是否意味着我在迭代大型多级数据结构时应该使用旧样式for循环?

Hol*_*ger 141

它相当但不完全相同.简单地说,如果lambda表达式没有捕获值,它将是在每次调用时重用的单例.

行为未明确指定.如何实现JVM给予了很大的自由.目前,Oracle的JVM为每个lambda表达式创建(至少)一个实例(即不在不同的相同表达式之间共享实例),但为不捕获值的所有表达式创建单例.

您可以阅读此答案以获取更多详细信息.在那里,我不仅给出了更详细的描述,还测试了代码来观察当前的行为.


这由Java®语言规范" 15.27.4 " 涵盖.Lambda表达式的运行时评估 "

总结如下:

这些规则旨在为Java编程语言的实现提供灵活性,其中:

  • 不需要在每次评估时分配新对象.

  • 由不同的lambda表达式生成的对象不需要属于不同的类(例如,如果主体是相同的).

  • 评估产生的每个对象不必属于同一个类(例如,捕获的局部变量可能被内联).

  • 如果"现有实例"可用,则无需在先前的lambda评估中创建它(例如,它可能在封闭类的初始化期间分配).

  • @HS 类似于内部类。访问周围上下文的局部变量将捕获它们的值,并在计算 lambda 表达式主体时使用该值。访问周围对象的实例字段需要捕获“this”引用的值,以便能够在计算 lambda 表达式主体时读取该字段。您可以点击答案中给出的链接([this one](/sf/answers/1679393761/))来获取实际示例。 (3认同)

Mar*_*nik 22

当敏感地创建表示lambda的实例时,取决于lambda体的确切内容.也就是说,关键因素是lambda 从词汇环境中捕获的内容.如果它没有捕获从创建到创建的任何变量状态,那么每次输入for-each循环时都不会创建实例.相反,将在编译时生成合成方法,并且lambda use site将仅接收委托给该方法的单个对象.

进一步注意,这方面是依赖于实现的,您可以期待HotSpot的未来改进和进步,以提高效率.有一般计划例如制作一个没有完整对应类的轻量级对象,它只有足够的信息转发到单个方法.

这是一篇关于该主题的优秀,可访问的深入文章:

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