Java lambda表达式是否每次在堆上创建对象';他被处决了?

Java lambda表达式是否每次在堆上创建对象';他被处决了?,java,lambda,java-8,Java,Lambda,Java 8,当我使用Java8的新语法糖迭代集合时,例如 myStream.forEach(item -> { // do something useful }); 这不等于下面的“旧语法”片段吗 myStream.forEach(new Consumer<Item>() { @Override public void accept(Item item) { // do something useful } }); myStream.forEach(新消费者()

当我使用Java8的新语法糖迭代集合时,例如

myStream.forEach(item -> {
  // do something useful
});
这不等于下面的“旧语法”片段吗

myStream.forEach(new Consumer<Item>() {
  @Override
  public void accept(Item item) {
    // do something useful
  }
});
myStream.forEach(新消费者(){
@凌驾
公共作废接受(项目){
//做些有用的事
}
});

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

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

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

这不等于“旧语法”片段吗


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

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

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

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


Java®语言规范第“”章对此进行了介绍

总结:

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

  • 不需要在每次评估时分配新对象

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

  • 通过求值生成的每个对象不必属于同一类(例如,捕获的局部变量可能是内联的)

  • 如果“现有实例”可用,则不需要在之前的lambda求值中创建它(例如,它可能是在封闭类的初始化过程中分配的)


创建表示lambda的实例的时间取决于lambda主体的确切内容。也就是说,关键因素是lambda从词汇环境中捕获的内容。如果它没有捕获从创建到创建的任何可变状态,则不会在每次输入for-each循环时创建实例。相反,将在编译时生成一个合成方法,lambda使用站点将只接收一个委托给该方法的单例对象

进一步注意,这一方面取决于实现,您可以期待将来对HotSpot进行改进和改进,以提高效率。有一些总体计划,例如,在没有完整的对应类的情况下创建一个轻量级对象,该类只有足够的信息转发给单个方法

以下是一篇关于该主题的深入文章:


您是否有任何文件对此进行了说明?这是一个非常有趣的优化。谢谢,但是如果我有一个集合集合,例如在树数据结构上进行深度优先搜索时,会怎么样?@a.Rama抱歉,我没有看到优化。有或没有lambdas,有或没有forEach循环都是一样的,实际上并不一样,但在任何时候每个嵌套级别最多需要一个对象,这是可以忽略的。内部循环的每个新迭代将创建一个新对象,很可能从外部循环捕获当前项。这造成了一些GC压力,但仍然没有什么需要担心的。@aalku:“每次你这样做,你都会创建一个新对象”:不是根据和。简短回答:否。对于无状态lambda(那些不从词法上下文捕获任何内容的lambda),只会创建一个实例(延迟),并缓存在捕获站点。(这就是实现的工作原理;规范是仔细编写的,允许但不要求这种方法。)