当我使用Java 8的新语法糖迭代一个集合时,例如
myStream.forEach(item -> {
// do something useful
});
这不等同于旧的语法'下面的片段?
myStream.forEach(new Consumer<Item>() {
@Override
public void accept(Item item) {
// do something useful
}
});
这是否意味着每次迭代集合时都会在堆上创建一个新的匿名Consumer
对象?这需要多少堆空间?它有什么性能影响?这是否意味着我在迭代大型多级数据结构时应该使用旧样式for循环?
答案 0 :(得分:126)
它是等同的但不完全相同。简单地说,如果lambda表达式没有捕获值,它将是在每次调用时重用的单例。
未明确指定行为。如何实现JVM给予了很大的自由。目前,Oracle的JVM为每个lambda表达式创建(至少)一个实例(即不在不同的相同表达式之间共享实例),但为所有不捕获值的表达式创建单例。
您可以阅读this answer了解更多详情。在那里,我不仅给出了更详细的描述,还测试了代码来观察当前的行为。
Java®语言规范,“15.27.4. Run-time Evaluation of Lambda Expressions”一章
涵盖了这一点总结:
这些规则旨在为Java编程语言的实现提供灵活性,其中包括:
不需要在每次评估时分配新对象。
由不同的lambda表达式生成的对象不必属于不同的类(例如,如果主体是相同的)。
评估产生的每个对象都不必属于同一个类(例如,可能内联捕获的局部变量)。
如果“现有实例”可用,则无需在先前的lambda评估中创建(例如,它可能在封闭类的初始化期间分配)。
答案 1 :(得分:22)
当敏感地创建表示lambda的实例时,取决于lambda体的确切内容。也就是说,关键因素是lambda 从词汇环境中捕获。如果它没有捕获从创建到创建的任何变量状态,那么每次输入for-each循环时都不会创建实例。相反,将在编译时生成一个合成方法,lambda use site将只接收一个委托给该方法的单例对象。
进一步请注意,此方面取决于实现,您可以期待HotSpot的未来改进和进步,以提高效率。有一般计划,例如制作一个没有完整对应类的轻量级对象,它只有足够的信息转发到单个方法。
这是一篇关于该主题的优秀,可访问的深入文章:
http://www.infoq.com/articles/Java-8-Lambdas-A-Peek-Under-the-Hood
答案 2 :(得分:0)
您正在将新实例传递给forEach
方法。每次执行此操作时,都会创建一个新对象,但不会为每个循环迭代创建一个对象。迭代是在forEach
方法内使用相同的回调&#39;对象实例,直到完成循环。
因此循环使用的内存不依赖于集合的大小。
这不等同于旧的语法&#39;段?
是。它在很低的水平上略有不同,但我认为你不应该关心它们。 Lamba表达式使用invokedynamic功能而不是匿名类。