Java 如何在lambda迭代和普通循环之间做出决定?

Java 如何在lambda迭代和普通循环之间做出决定?,java,lambda,jvm,java-8,iteration,Java,Lambda,Jvm,Java 8,Iteration,自从他介绍Java8以来,我就非常喜欢lambdas,并开始尽可能地使用它们,主要是为了开始习惯它们。最常见的用法之一是当我们想要迭代对象集合并对其执行操作时,在这种情况下,我要么求助于forEach要么求助于stream()。我很少为(T:Ts)循环编写旧的,我几乎忘记了(int I=0…)的 然而,前几天我们和我的主管讨论过这个问题,他告诉我,lambdas并不总是最好的选择,有时会影响性能。从一次关于这个新特性的讲座中,我感觉到lambda迭代总是被编译器完全优化的,并且(总是?)会比裸迭

自从他介绍Java8以来,我就非常喜欢lambdas,并开始尽可能地使用它们,主要是为了开始习惯它们。最常见的用法之一是当我们想要迭代对象集合并对其执行操作时,在这种情况下,我要么求助于
forEach
要么求助于
stream()
。我很少为(T:Ts)循环编写旧的
,我几乎忘记了(int I=0…)的

然而,前几天我们和我的主管讨论过这个问题,他告诉我,lambdas并不总是最好的选择,有时会影响性能。从一次关于这个新特性的讲座中,我感觉到lambda迭代总是被编译器完全优化的,并且(总是?)会比裸迭代更好,但他要求有所不同。这是真的吗?如果是,我如何区分每个场景中的最佳解决方案


注:我不是在谈论建议应用并行流的情况。显然,这将更快。

这取决于具体的实现

一般来说,
forEach
方法和
forEach
循环
Iterator
通常具有非常相似的性能,因为它们使用相似的抽象级别
stream()
通常较慢(通常为50-70%),因为它添加了另一个提供对基础集合访问的级别


stream()
的优点通常是可能的并行性和操作与JDK提供的许多可重用操作的轻松链接。

性能取决于许多因素,因此很难预测。通常,我们会说,如果您的主管声称绩效存在问题,您的主管将负责解释问题所在

有人可能害怕的一件事是,在幕后,为每个lambda创建站点(使用当前实现)生成一个类,因此如果所讨论的代码只执行一次,这可能会被视为浪费资源。这与lambda表达式作为普通命令代码具有更高初始化开销的事实相协调(我们不在这里与内部类进行比较),所以在类初始化器中,只运行一次,您可以考虑避免它。这也符合你应该这样做的事实,所以这个潜在的优势在这里是不可用的

对于可能由JVM优化的普通、频繁执行的代码,这些问题不会出现。正如您正确假设的那样,为lambda表达式生成的类与其他类得到相同的处理(优化)。在这些地方,对集合调用
forEach
可能比
for
循环更有效

迭代器
或lambda表达式创建的临时对象实例可以忽略不计,但是,值得注意的是,foreach循环将始终创建一个
迭代器
实例。虽然
Iterable.forEach的
default
实现也将创建一个
迭代器
,但一些最常用的集合借此机会提供了专门的实现,最显著的是
ArrayList

ArrayList
的forEach
基本上是数组上的
for
循环,没有任何
迭代器。然后,它将调用
使用者
accept
方法,该方法将是一个生成的类,其中包含对包含lambda表达式代码的合成方法的一个简单委托。要优化整个循环,优化器的范围必须跨越
ArrayList
在数组上的循环(优化器可以识别的一个常见习惯用法)、包含一个小委托的合成
accept
方法和包含实际代码的方法


相反,当使用foreach循环对同一列表进行迭代时,将创建一个
迭代器
实现,其中包含
ArrayList
迭代逻辑,该逻辑分布在两个方法上,
hasNext()
next()
以及
迭代器的实例变量。循环将重复调用
hasNext()
方法来检查结束条件(
如果CPU需求和/或流大小不大,则并行流可能比正常循环慢。线程管理成本很高。流框架的开销可能会令人惊讶。测量!确实如此,因此我更新了我的问题。请看一看使用您认为对您来说更易于编写/理解的方法。这将有所不同在不同的时间使用ent,并且您了解如何更好地使用lambda。切中要害的是,
List.forEach
不会对性能造成任何损失,但不幸的是,它没有
List.stream/().forEach
。我不同意流速度慢50-70%的说法。在大多数情况下,这些数字源于测量首次开销的不正确基准。