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

问题描述 投票:172回答:3

[当我使用Java 8的新语法糖对集合进行迭代时,例如

myStream.forEach(item -> {
  // do something useful
});

这不等同于下面的“旧语法”代码段吗?

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

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

java lambda java-8
3个回答
148
投票

这是等效但不相同。简而言之,如果lambda表达式不能捕获值,那么它将是单例,将在每次调用时重复使用。

行为未完全指定。 JVM在如何实现它方面有很大的自由度。当前,Oracle的JVM为每个lambda表达式创建(至少)一个实例(即,不在不同的相同表达式之间共享实例),但为所有不捕获值的表达式创建单例。

您可以阅读this answer了解更多详细信息。在那里,我不仅给出了更详细的描述,还提供了测试代码以观察当前行为。


这在Java®语言规范的“ 15.27.4. Run-time Evaluation of Lambda Expressions”一章中有介绍

摘要:

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

  • 不需要在每次评估中分配一个新对象。

  • 由不同的lambda表达式产生的对象不必属于不同的类(例如,如果主体相同)。]]

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

  • 如果“现有实例”可用,则不需要在先前的lambda评估中创建它(例如,可能在封闭类的初始化期间分配了它。)>

创建表示lambda的实例时,敏感地取决于lambda身体的确切内容。即,关键因素是词法环境中的lambda captures。如果它没有捕获创建到创建之间可变的任何状态,则不会在每次进入for-each循环时创建实例。相反,将在编译时生成综合方法,并且lambda使用站点将仅接收委托给该方法的单例对象。

还要注意,这方面与实现有关,您可以期望HotSpot将来会进行改进和改进,以提高效率。有一些总体计划,例如创建一个没有完整对应类的轻量级对象,该类仅具有足够的信息以转发给单个方法。

这里有一篇很好的,可访问的有关该主题的深入文章:

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

您正在将新实例传递给forEach方法。每次执行此操作时,都会创建一个新对象,但每次循环迭代都不会创建一个对象。使用相同的“回调”对象实例在forEach方法内部进行迭代,直到通过循环完成为止。

因此循环使用的内存不取决于集合的大小。

这不等同于“旧语法”代码段吗?

是。它在非常低的水平上有细微的差异,但我认为您不应该在意它们。 Lamba表达式使用invokedynamic功能而不是匿名类。


22
投票

创建表示lambda的实例时,敏感地取决于lambda身体的确切内容。即,关键因素是词法环境中的lambda captures。如果它没有捕获创建到创建之间可变的任何状态,则不会在每次进入for-each循环时创建实例。相反,将在编译时生成综合方法,并且lambda使用站点将仅接收委托给该方法的单例对象。


0
投票

您正在将新实例传递给forEach方法。每次执行此操作时,都会创建一个新对象,但每次循环迭代都不会创建一个对象。使用相同的“回调”对象实例在forEach方法内部进行迭代,直到通过循环完成为止。

因此循环使用的内存不取决于集合的大小。

© www.soinside.com 2019 - 2024. All rights reserved.