列表的奇怪行为

14
我已经将列表从0-9(整数)定义如下:
List<Integer> list = 
                IntStream.range(0, 10)
                         .boxed()
                         .collect(Collectors.toCollection(ArrayList::new));

当我尝试使用以下代码删除元素时:

list.stream()
            .peek(list::remove)
            .forEach(System.out::println);

应该抛出 ConcurrentModificationException,但有趣的是它对某些元素起作用,并给出以下输出(在最后抛出异常并删除了一些元素):

0
2
4
6
8
null
null
null
null
null
Exception in thread "main" java.util.ConcurrentModificationException

但是,如果我像下面这样添加了sorted()

list.stream()
            .sorted()
            .peek(list::remove)
            .forEach(System.out::println);

这完美地运行并且删除了所有元素,我不明白为什么stream会以这种方式表现。

1个回答

11

你看过这些类是如何实现的吗?

sorted() 方法会复制一个新列表来进行排序。

因此它的行为不同:你是在遍历已排序的副本,但只从原始列表中删除。

不建议使用 peek()

peek 方法不建议使用;来自JavaDoc的说明:

该方法主要用于支持调试

可以在开发期间放置一个 System::println 或类似的东西,但这不是最终程序应该使用的功能。

永远不要尝试修改当前流

阅读流包的 API 规范:

https://docs.oracle.com/javase/8/docs/api/java/util/stream/package-summary.html#NonInterference

对于大多数数据源,防止干扰意味着确保在流管道执行过程中不修改数据源。

换句话说:您不允许从您当前流式处理的列表中删除任何内容,没有保证它将起作用。

正在发生什么

您的初始列表是

[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]        pos = 0

你打印出0并将其删除,然后移动到下一个位置。新的数组变成:

[1, 2, 3, 4, 5, 6, 7, 8, 9, null]     pos = 1

下一个位置,该值现在为2,因此打印并移除2

[1, 3, 4, 5, 6, 7, 8, 9, null, null]  pos = 2

等等,问题在于由于您的并发删除,列表的余下部分继续向前移动。

ConcurrentModificationException 异常在流的结尾抛出。这似乎是性能决策(仅对 forEachRemaining 进行一次并发修改检查,而不是在每次迭代时进行检查),因此此流不是“快速失败”的。


我认为你已经明白了,当你添加 .parallel() 的特性时,它的行为完全不同,所以我猜我们在使用 stream 时应该避免删除元素,因为不能修改支持集合。 - Nitesh Virani
仍然没有回答为什么在第一种情况下某些元素被删除或未被删除。 - Mordechai
这是因为在移除 0 后,下一个元素是 2。每个偶数元素都被移除,奇数元素向上移动列表。 - Has QUIT--Anony-Mousse

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接