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