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

问题

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

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

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

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

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


#1 热门回答(105 赞)

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

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

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

这由Java®语言规范"15.27.4. Run-time Evaluation of Lambda Expressions"章节涵盖。

总结如下:

这些规则旨在为Java编程语言的实现提供灵活性,其中:不需要在每个评估上分配新对象。由不同的lambda表达式生成的对象不需要属于不同的类(例如,如果主体是相同的)。评估产生的每个对象不必属于同一个类(例如,可能内联捕获的局部变量)。如果"现有实例"可用,则无需在先前的lambda评估中创建它(例如,它可能在封闭类的初始化期间分配)。


#2 热门回答(19 赞)

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

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

这是一篇关于该主题的优秀,可访问的深入文章:
http://www.infoq.com/articles/Java-8-Lambdas-A-Peek-Under-the-Hood


#3 热门回答(0 赞)

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

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

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

是。它在很低的水平上有轻微的差异,但我认为你不应该关心它们。 Lamba表达式使用invokedynamic功能而不是匿名类。