在 Java Streams 中,建议避免在 foreach 中使用共享状态。
证明:
java 流文档
副作用 一般来说,不鼓励流操作的行为参数产生副作用,因为它们通常会导致无意中违反无状态性要求,以及其他线程安全危险。 如果行为参数确实有副作用,除非明确说明,否则无法保证这些副作用对其他线程的可见性,也无法保证同一流管道中“相同”元素上的不同操作都在同一个线程中执行。此外,这些影响的顺序可能会令人惊讶。即使管道被限制为生成与流源的遇到顺序一致的结果(例如,IntStream.range(0,5).parallel().map(x -> x*2).toArray( ) 必须产生 [0, 2, 4, 6, 8]),不保证映射器函数应用于各个元素的顺序,也不保证在哪个线程中为给定元素执行任何行为参数。
许多可能想使用副作用的计算可以更安全、更有效地表达而没有副作用,例如使用归约而不是可变累加器。然而,诸如使用 println() 进行调试之类的副作用通常是无害的。少数流操作,例如 forEach() 和 peek(),只能通过副作用进行操作;这些应该小心使用。
作为如何将不恰当地使用副作用的流管道转换为不使用副作用的流管道的示例,以下代码在字符串流中搜索与给定正则表达式匹配的字符串,并将匹配项放入列表中。
ArrayList<String> results = new ArrayList<>();
stream.filter(s -> pattern.matcher(s).matches())
.forEach(s -> results.add(s)); // Unnecessary use of side-effects!
此代码不必要地使用副作用。如果并行执行,ArrayList 的非线程安全性会导致不正确的结果,并且添加所需的同步会导致争用,从而破坏并行性的好处。此外,在这里使用副作用是完全没有必要的; forEach() 可以简单地替换为更安全、更高效且更适合并行化的归约操作:
List<String>results =
stream.filter(s -> pattern.matcher(s).matches())
.collect(Collectors.toList()); // No side-effects!
我找不到 Kotlin 的相同推荐。
我应该在 Kotlin 中避免这样的代码吗?
var bar = foo(myPath)
myPath.forEach { e ->
...
bar = foo(bar,....)
}
kotlin.collections
包中的函数(forEach
就是其中之一)永远不会同时运行。因此,引用的javadocs中提到的线程安全问题并不相关。
forEach
(kotlin.collections
中的许多其他函数)是一个inline
函数。这意味着它的函数体在编译期间直接插入到调用站点中。您传递给它的 lambda also 是内联的。
通过检查源,我们可以看到调用
forEach
与for
循环完全相同。
list.forEach { f(it) }
内联到
for (it in list) {
f(it)
}
所以在编译后的二进制文件中,不再有对
forEach
的调用,也不再有lamnbda。该代码的行为与您编写 for
循环而不是调用 forEach
完全相同。