为什么这段代码不会抛出ConcurrentModificationException
?它会在迭代通过它时修改Collection
,而不使用Iterator.remove()
方法,这意味着the only safe way of removing。
List<String> strings = new ArrayList<>(Arrays.asList("A", "B", "C"));
for (String string : strings)
if ("B".equals(string))
strings.remove("B");
System.out.println(strings);
如果我用ArrayList
替换LinkedList
,我会得到相同的结果。但是,如果我将列表更改为("A", "B", "C", "D)
或仅("A", "B")
,我会按预期获得异常。到底是怎么回事?如果相关,我正在使用jdk1.8.0_25
。
编辑
我找到了以下链接
http://bugs.java.com/bugdatabase/view_bug.do?bug_id=4902078
相关部分是
天真的解决方案是在AbstractList中为hasNext添加编码检查,但这会使编纂检查的成本翻倍。 事实证明,仅在最后一次测试就足够了 迭代,几乎没有增加成本。换一种说法, hasNext的当前实现:
public boolean hasNext() { return nextIndex() < size; }
替换为此实现:
public boolean hasNext() { if (cursor != size()) return true; checkForComodification(); return false; }
由于Sun内部监管机构拒绝了此更改,因此不会进行此更改。正式裁决表明这一变化“已经有了 证明了具有显着的兼容性影响的潜力 现有代码。“(”兼容性影响“是该修复程序具有的 用一种方法取代沉默的不良行为的可能性 ConcurrentModificationException的。)
答案 0 :(得分:22)
作为一般规则,当检测到,而不是导致时,会抛出ConcurrentModificationException
个。如果您在修改后从未访问过迭代器,则不会抛出异常。遗憾的是,这个细节使得ConcurrentModificationException
对于检测数据结构的滥用是相当不可靠的,因为它们只是在损坏完成后被抛出。
此方案不会抛出ConcurrentModificationException
,因为修改后next()
在创建的迭代器上没有被调用。
for-each循环实际上是迭代器,所以你的代码实际上是这样的:
List<String> strings = new ArrayList<>(Arrays.asList("A", "B", "C"));
Iterator<String> iter = strings.iterator();
while(iter.hasNext()){
String string = iter.next();
if ("B".equals(string))
strings.remove("B");
}
System.out.println(strings);
考虑您提供的列表上运行的代码。迭代看起来像:
hasNext()
返回true,输入循环, - &gt; iter移动到索引0,字符串=&#34; A&#34;,未删除hasNext()
返回true,继续循环 - &gt; iter移动到索引1,string =&#34; B&#34;,删除。 strings
现在的长度为2。hasNext()
返回false(iter当前位于最后一个索引,不再有索引),退出循环。因此,当ConcurrentModificationException
的调用检测到已经进行了修改时,抛出next()
,这种情况可以避免这种异常。
对于您的其他两个结果,我们会得到例外。对于"A", "B", "C", "D"
,删除&#34; B&#34;我们仍在循环中,next()
检测到ConcurrentModificationException
,而对于"A", "B"
,我想象它是某种被捕获的ArrayIndexOutOfBounds并以ConcurrentModificationException
答案 1 :(得分:9)
hasNext
只是
public boolean hasNext() {
return cursor != size;
}
在remove
调用之后,迭代器在索引2处,并且列表的大小为2,因此它报告迭代已完成。没有并发修改检查。使用(“A”,“B”,“C”,“D”或(“A”,“B”),迭代器不在列表的新末尾,因此调用next
,并且抛出异常。
ConcurrentModificationException
只是一个调试辅助工具。你不能依赖它们。
答案 2 :(得分:1)
@Tavian Barnes是完全正确的。如果有问题的并发修改未同步,则无法保证抛出此异常。引自java.util.ConcurrentModification
规范:
请注意,通常情况下无法保证快速失败的行为 说话,在场的情况下不可能做出任何硬性保证 不同步的并发修改。失败快速操作投掷 ConcurrentModificationException是尽力而为的。因此,它 编写一个依赖于此异常的程序是错误的 它的正确性:应该只使用ConcurrentModificationException 检测错误。