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

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

这难道不等同于下面的“旧语法”片段吗?

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

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


当前回答

您正在向forEach方法传递一个新实例。每次这样做时,都会创建一个新对象,但不是每次循环迭代都创建一个新对象。迭代是在forEach方法中使用相同的“回调”对象实例完成的,直到循环完成。

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

这难道不等同于“旧语法”片段吗?

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

其他回答

When an instance representing the lambda is created sensitively depends on the exact contents of your lambda's body. Namely, the key factor is what the lambda captures from the lexical environment. If it doesn't capture any state which is variable from creation to creation, then an instance will not be created each time the for-each loop is entered. Instead a synthetic method will be generated at compile time and the lambda use site will just receive a singleton object that delegates to that method.

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

下面是一篇关于这个主题的好文章:

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

它们是等价的,但不完全相同。简单地说,如果lambda表达式不捕获值,那么它将是在每次调用中重用的单例。

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

你可以阅读这个答案了解更多细节。在那里,我不仅给出了更详细的描述,而且还测试了代码来观察当前的行为。


这在Java®语言规范的15.27.4章中有介绍。Lambda表达式的运行时求值"

总结:

These rules are meant to offer flexibility to implementations of the Java programming language, in that: A new object need not be allocated on every evaluation. Objects produced by different lambda expressions need not belong to different classes (if the bodies are identical, for example). Every object produced by evaluation need not belong to the same class (captured local variables might be inlined, for example). If an "existing instance" is available, it need not have been created at a previous lambda evaluation (it might have been allocated during the enclosing class's initialization, for example).

您正在向forEach方法传递一个新实例。每次这样做时,都会创建一个新对象,但不是每次循环迭代都创建一个新对象。迭代是在forEach方法中使用相同的“回调”对象实例完成的,直到循环完成。

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

这难道不等同于“旧语法”片段吗?

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