当我使用 Java 8 的新语法糖迭代集合时,例如
myStream.forEach(item -> { // do something useful });
这不等同于下面的“旧语法”片段吗?
myStream.forEach(new Consumer<Item>() { @Override public void accept(Item item) { // do something useful } });
Consumer这是否意味着每次迭代集合时都会在堆上创建一个新的匿名对象?这需要多少堆空间?它有什么性能影响?这是否意味着在迭代大型多级数据结构时我应该使用旧样式的循环?
Consumer
它是等价的,但不完全相同。简单地说,如果 lambda 表达式不捕获值,它将是一个单例,在每次调用时都会重复使用。
该行为未完全指定。JVM 在如何实现它方面拥有很大的自由。目前,Oracle 的 JVM 为每个 lambda 表达式创建(至少)一个实例(即,不在不同的相同表达式之间共享实例),但为所有不捕获值的表达式创建单例。
您可以阅读此答案以获取更多详细信息。在那里,我不仅给出了更详细的描述,还测试了代码以观察当前行为。
Java® 语言规范第 15.27.4 章对此进行了介绍。Lambda表达式的运行时评估——
总结:
这些规则旨在为 Java 编程语言的实现提供灵活性,因为: 不需要在每次评估时都分配一个新对象。 由不同 lambda 表达式生成的对象不必属于不同的类(例如,如果主体相同)。 评估产生的每个对象不必属于同一个类(例如,捕获的局部变量可能是内联的)。 如果“现有实例”可用,则不必在先前的 lambda 评估中创建它(例如,它可能已在封闭类的初始化期间分配)。
这些规则旨在为 Java 编程语言的实现提供灵活性,因为:
不需要在每次评估时都分配一个新对象。
由不同 lambda 表达式生成的对象不必属于不同的类(例如,如果主体相同)。
评估产生的每个对象不必属于同一个类(例如,捕获的局部变量可能是内联的)。
如果“现有实例”可用,则不必在先前的 lambda 评估中创建它(例如,它可能已在封闭类的初始化期间分配)。