And*_* k. 2 lambda java-8 java-stream
通常,在进行迭代时进行修改会立即引发ConcurrentModificationException。
但是,使用流API的forEachOrdered方法,每次修改后都不会引发Exception。取而代之的是,修改可以在迭代过程中成功进行,并且最终在整个操作完成后会引发异常。有人可以告诉我这是怎么发生的吗?
下面的代码生成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);
Run Code Online (Sandbox Code Playgroud)
{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)
Run Code Online (Sandbox Code Playgroud)
如文档所述:
注意,迭代器的快速失败行为无法得到保证,因为通常来说,在存在不同步的并发修改的情况下,不可能做出任何严格的保证。快速失败的迭代器会
ConcurrentModificationException尽力而为。因此,编写依赖于此异常的程序的正确性是错误的:迭代器的快速失败行为应仅用于检测错误。
您的示例中不涉及并发,但是重点仍然存在。此功能仅用于检测错误,不能保证是否或何时引发异常。为了检测错误,就足够,如果抛出异常,即使只在迭代结束。
如果使用hasNext()和的next()方法Iterator,则无法控制调用者。不需要调用者迭代到集合的末尾,换句话说,每个方法调用可以是Iterator被放弃之前的最后一个方法。因此,这些方法不能推迟检查。
相比之下,forEachRemaining方法Iterator和Spliterator在迭代循环的控制,并知道他们什么时候会退出,因此,可以省略在每次迭代检查,并在年底进行单次检查,假设我们有一个专门的实现,而不是default方法只能重复调用单元素迭代方法。
正如您在stacktrace中看到的那样,forEachOrderedStream操作最终在HashMap$EntrySpliterator.forEachRemaining,这是一个抓住机会的专门实现。显而易见的原因是性能,因为您不想浪费CPU周期来测试应该在每次迭代中进行的条件,但前提是false合同允许在最后一次进行一次测试(如果我们从最佳案例中得出要求,努力基础合同)。
| 归档时间: |
|
| 查看次数: |
52 次 |
| 最近记录: |