Java:为啥在使用 Stream+Iterator 时不会发生 ConcurrentModificationException?

Posted

技术标签:

【中文标题】Java:为啥在使用 Stream+Iterator 时不会发生 ConcurrentModificationException?【英文标题】:Java: Why ConcurrentModificationException doesnt occur while using Stream+Iterator?Java:为什么在使用 Stream+Iterator 时不会发生 ConcurrentModificationException? 【发布时间】:2020-10-21 07:54:02 【问题描述】:

我正在阅读一些关于迭代器、枚举等的信息。所以我测试了一些代码来检查所有内容。

第一个例子:

List<String> list = new ArrayList<>();
list.add("5");
list.add("1");
list.add("2");
list.add("3");
Iterator<String> iterator = list.iterator();
list.remove("1");
iterator.forEachRemaining(System.out::println);

此代码产生正确且预期的输出:

Exception in thread "main" java.util.ConcurrentModificationException

但是当我测试相同的代码时只做了 1 处更改 list.iterator() -> list.stream().iterator()

List<String> list = new ArrayList<>();
list.add("5");
list.add("1");
list.add("2");
list.add("3");
Iterator<String> iterator = list.stream().iterator();
list.remove("1");
iterator.forEachRemaining(System.out::println);

结果是:

5
2
3

我的主要目的是了解:

    为什么会这样? 什么魔法使只有一个stream() 方法? 使用stream().iterator的优缺点是什么?

【问题讨论】:

【参考方案1】:

当您调用stream() 时,API 内部调用spliterator(),它类似于iterator()

对于ArrayList,返回的Spliterator / Iterator 使用expectedModCount 字段与列表的modCount 字段进行比较,以检查列表是否已被修改。

Iterator的实现在创建迭代器时初始化expectedModCount字段。

Spliterator 的实现延迟初始化,直到第一个元素取自 Spliterator

这意味着它允许在对stream() 的调用和对实际开始从流中提取数据的终端操作的调用之间修改列表。

【讨论】:

这些是技术(实施)细节。更重要的方面是ArrayListstream() 方法返回一个late-binding Stream。它还在the Non-interference section of the package documentation 中指定:“对于行为良好的流源,可以在终端操作开始之前修改源,这些修改将反映在覆盖的元素中。”例如......

以上是关于Java:为啥在使用 Stream+Iterator 时不会发生 ConcurrentModificationException?的主要内容,如果未能解决你的问题,请参考以下文章

为啥 Java 8 Stream 上没有直接存在 toList() [重复]

Java8中Stream类中的forEach是抽象方法,为啥在调用的时候不用重写该方法就能实现遍历?

为啥具有短路操作的并行 Java Stream 会评估 Stream 的所有元素,而顺序 Stream 不会?

java stream 为啥不能重用

Java编程系列Java8使用List的Stream收集转换成Set,数据却丢失了,你知道为啥吗?

Java编程系列Java8使用List的Stream收集转换成Set,数据却丢失了,你知道为啥吗?