根据oracle网站上的文档:
一般来说,不鼓励行为参数对流操作产生副作用,因为它们通常会导致无意中违反无状态性要求,以及其他线程安全危险。
这包括将流的元素保存到数据库吗?
想象一下下面的(伪)代码:
public SavedCar saveCar(Car car) {
SavedCar savedCar = this.getDb().save(car);
return savedCar;
}
public List<SavedCars> saveCars(List<Car> cars) {
return cars.stream()
.map(this::saveCar)
.collect(Collectors.toList());
}
与此实施相反的有害影响有哪些:
public SavedCar saveCar(Car car) {
SavedCar savedCar = this.getDb().save(car);
return savedCar;
}
public List<SavedCars> saveCars(List<Car> cars) {
List<SavedCars> savedCars = new ArrayList<>();
for (Cat car : cars) {
savedCars.add(this.saveCar(car));
}
return savedCars.
}
2条答案
按热度按时间jjjwad0x1#
根据oracle网站上的文档[…]
该链接适用于Java8。您可能需要阅读Java9(于2017年发布)和更高版本的文档,因为它们在这方面更为明确。明确地:
流实现在优化结果的计算方面是允许的。例如,流实现可以从流管道中省略操作(或整个阶段),因此可以省略行为参数的调用,前提是它可以证明它不会影响计算结果。这意味着行为参数的副作用可能并不总是被执行,也不应该被依赖,除非另有规定(例如终端操作)
forEach
以及forEachOrdered
). (有关这种优化的具体示例,请参阅count()
操作。有关更多详细信息,请参阅流包文档的“副作用”部分。)来源:java9的javadoc
Stream
接口。还有你引用的文件的更新版本:
副作用
一般来说,不鼓励行为参数对流操作产生副作用,因为它们通常会导致无意中违反无状态性要求,以及其他线程安全危险。
如果行为参数确实有副作用,除非明确说明,否则不保证:
这些副作用对其他线程的可见性;
在同一个线程中对同一流管道中的“同一”元素执行不同的操作;和
行为参数总是被调用的,因为流实现可以自由地从流管道中删除操作(或整个阶段),如果它可以证明它不会影响计算结果的话。
副作用的顺序可能令人惊讶。即使管道被约束以产生与流源的相遇顺序一致的结果(例如,
IntStream.range(0,5).parallel().map(x -> x*2).toArray()
必须产生[0, 2, 4, 6, 8]
),不保证Map器函数应用于单个元素的顺序,也不保证为给定元素在哪个线程中执行任何行为参数。副作用的消除也可能令人惊讶。终端操作除外
forEach
以及forEachOrdered
当流实现可以在不影响计算结果的情况下优化行为参数的执行时,行为参数的副作用可能并不总是被执行(有关特定示例,请参见count
操作。)来源:java9的javadoc
java.util.stream
包裹。都是我的。
如您所见,当前的官方文档对您决定在流操作中使用副作用时可能遇到的问题进行了更详细的说明。这一点也非常清楚
forEach
以及forEachOrdered
作为唯一一个保证执行副作用的终端操作(请注意,线程安全问题仍然适用,正如官方示例所示)。也就是说,关于您的特定代码,以及仅上述代码:
我看不到与所说的代码流相关的问题。
这个
.map()
将执行步骤,因为.collect()
(一个可变的缩减操作,这是官方文件建议的,而不是.forEach(list::add)
)依赖.map()
的输出。saveCar()
的)输出与输入不同,流不能“证明[排除]它不会影响计算结果”。它不是一个
parallelStream()
因此,它不应该引入任何以前不存在的并发问题(当然,如果有人添加了.parallel()
之后可能会出现问题——就像有人决定将for
通过为内部计算启动新线程进行循环)。这并不意味着该示例中的代码是好代码™. 顺序
.stream.map(::someSideEffect()).collect()
作为一种执行副作用的方法,对集合中的每一项进行操作可能看起来更简单/简短/优雅?比它的for
对应的,有时可能是。然而,正如尤金,霍尔格和其他一些人告诉你的,有更好的方法来解决这个问题。作为一个快速思考:启动一个
Stream
vs迭代一个简单的for
除非您有很多项,否则是不可忽略的,如果您有很多项,那么您:a)可能不想为每个项创建一个新的db访问,所以a)saveAll(List items)
api更好;b)可能不想承受按顺序处理大量项目的性能损失,因此最终会使用并行化,然后会出现一组全新的问题。vaqhlq812#
最简单的例子是:
在这种情况下,从java-9和更高版本,
map
不会被执行;既然你不需要它来了解count
,完全没有。还有其他多种情况下,副作用会导致你很多痛苦;在某些条件下。