在 Java Streams 中建议避免在 foreach 中使用共享状态。
证明:
java streams doc
副作用 一般情况下,不鼓励在流操作的行为参数中使用副作用,因为它们通常会导致无意中违反无状态要求,以及其他线程安全隐患。如果行为参数确实有副作用,除非明确说明,否则无法保证这些副作用对其他线程的可见性,也无法保证在同一流管道中对“相同”元素的不同操作在同一线程中执行。此外,这些影响的顺序可能令人惊讶。即使管道被限制为产生与流源的遇到顺序一致的结果(例如,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,....)
}
与 Java 的流可以并行运行不同,
kotlin.collections
包中的函数(forEach
是其中之一)永远不会并发运行。因此,引用的 javadoc 中提到的线程安全问题并不相关。forEach
(以及 中的许多其他函数kotlin.collections
)是一个inline
函数。这意味着它的函数体在编译期间直接插入到调用站点中。传递给它的 lambda也是内联的。通过检查源代码,我们可以看到调用
forEach
与循环完全相同for
。内联至
因此,在编译后的二进制文件中,不再有对 的调用
forEach
,也不再有 lamnbda。代码的行为与编写循环for
而不是调用 时的行为完全相同forEach
。