Stream API 中的 forEach
Java 8 引入的 Stream API 为数据集合提供了一种高效且声明式的处理方式。forEach
方法作为 Stream API 中的终端操作之一,其作用是对流中的每一个元素执行特定的操作。为了深入了解 forEach
的内部工作机制,我们需要探讨 Stream API 的实现细节,特别是 Spliterator
和 ForkJoinPool
的角色。
Spliterator
Spliterator
(可拆分迭代器)是 Stream API 的核心组件,它不仅提供了遍历数据的能力,还支持数据源的分割,这使其非常适合并行处理。Spliterator
具有如下特性:
1. 遍历:按序遍历元素。
2. 分割:将数据源分割成多个部分,每部分可以被不同线程独立处理。
3. 特性描述:表达数据源的属性,比如是否排序、能否并行处理等。
4. 大小估算:估计尚未遍历的元素数量。
顺序流中的 forEach
在顺序流中,forEach
通过调用 Spliterator
的 tryAdvance
方法来遍历所有元素。此方法尝试对当前元素执行指定动作,若成功则返回 true
,否则返回 false
表示无更多元素可处理。
并行流中的 forEach
并行流中 forEach
的实现更为复杂,因为它需要使用多线程来并行处理数据。这里涉及到 ForkJoinPool
以及 Spliterator
的分割机制。
ForkJoinPool
ForkJoinPool
是 Java 中一种特殊的线程池,专为能够分解为更小子任务的任务设计。它运用了工作窃取算法来均衡线程间的工作负荷。当某一线程完成自身任务后,可以从其他线程的任务队列中获取额外任务以执行,从而提升整体执行效率。
惰性求值
Stream API 遵循惰性求值原则,即中间操作并不会立即执行,直到遇到终端操作(如 forEach
)才触发整个管道的计算。这一机制让 Stream API 可以在最终执行前进行优化,如决定是否启用并行处理。
forEach
方法的并发修改问题
在使用 forEach
方法遍历集合时尝试修改该集合,可能会引发 ConcurrentModificationException
,尤其是在顺序流的情况下。对于并行流,虽然未必直接抛出异常,但这种做法依然可能导致不可预知的行为或并发问题。
如何安全地修改集合
为了避免此类问题,可以采取以下策略:
1. 使用 removeIf
方法:
list.removeIf(s -> "b".equals(s));
2. 通过迭代器安全地移除元素:
Iterator<String> it = list.iterator();
while (it.hasNext()) {if ("b".equals(it.next())) {it.remove();}
}
3. 创建过滤后的副本:
List<String> filteredList = list.stream().filter(s -> !"b".equals(s)).collect(Collectors.toList());
综上所述,在顺序或并行流中直接修改正在被遍历的集合是不推荐的,应考虑使用上述推荐方法以保证程序的稳定性和安全性。