通常,在进行迭代的同时进行修改时,会立即引发ConcurrentModificationException。
但是,使用流API的forEachOrdered方法,每次修改后都不会引发异常。取而代之的是,修改可以在迭代过程中成功进行,并且最终在整个操作完成后会引发异常。有人可以告诉我这是怎么发生的吗?
下面的代码生成3个随机数,范围从-0.5到0.5。然后过滤掉负值。
HashMap<Integer, Double> positiveNegative = new HashMap<>();
for (int i = 0; i < 3; i++) {
double value =Math.random() - 0.5;
positiveNegative.put(i, value);
}
System.out.println(positiveNegative);
//filter out the negative value
positiveNegative.entrySet().stream()
.filter((entry) -> (entry.getValue() < 0))
.forEachOrdered(entry -> {
System.out.println(entry);
positiveNegative.remove(entry.getKey());
System.out.println(positiveNegative);
});
System.out.println(positiveNegative);
{0=-0.38785299800912576, 1=-0.11085161629013052, 2=-0.3516129030809366}
0=-0.38785299800912576
{1=-0.11085161629013052, 2=-0.3516129030809366}
1=-0.11085161629013052
{2=-0.3516129030809366}
2=-0.3516129030809366
{}
Exception in thread "main" java.util.ConcurrentModificationException
at java.base/java.util.HashMap$EntrySpliterator.forEachRemaining(HashMap.java:1751)
at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:484)
at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:474)
at java.base/java.util.stream.ForEachOps$ForEachOp.evaluateSequential(ForEachOps.java:150)
at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.evaluateSequential(ForEachOps.java:173)
at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
at java.base/java.util.stream.ReferencePipeline.forEachOrdered(ReferencePipeline.java:502)
at Solution.main(Solution.java:51)
请注意,不能保证迭代器的快速失败行为,因为通常来说,在存在不同步的并发修改的情况下,不可能做出任何严格的保证。快速失败的迭代器会尽最大努力抛出
ConcurrentModificationException
。因此,编写依赖于此异常的程序的正确性是错误的:迭代器的快速失败行为应仅用于检测错误。
您的示例中不涉及并发,但重点仍然是;此功能仅用于检测错误,不能保证是否或何时引发异常。要检测该错误,即使仅在迭代结束时,抛出异常[[if也已足够。
在hasNext()
的next()
和Iterator
方法的情况下,无法控制调用者。不需要调用者迭代到集合的末尾,换句话说,在放弃Iterator
之前,每个方法调用都可以是最后一个。因此,这些方法无法推迟检查。[相反,forEachRemaining
和Iterator
的Spliterator
方法在迭代循环中,并且知道它们何时退出,因此可以忽略每次迭代中的检查并在最后执行一次检查,假设我们有一个专门的实现,而不是default
方法,该方法别无选择,只能反复调用单元素迭代方法。
如您在堆栈跟踪中所看到的,forEachOrdered
流操作最终以HashMap$EntrySpliterator.forEachRemaining
结尾,这是利用这一机会的专门实现。显而易见的原因是性能,因为当合同允许最后一次执行此操作时,无论如何您都不想浪费CPU周期来测试应该为false
的每次迭代中的条件(如果我们从一份
尽力而为]基础合同)。