为什么这段代码不会抛出ConcurrentModificationException?

为什么这段代码不会抛出ConcurrentModificationException ? 它在迭代过程中修改了Collection ,而没有使用Iterator.remove()方法,这意味着它是唯一安全的删除方法 。

 List strings = new ArrayList(Arrays.asList("A", "B", "C")); for (String string : strings) if ("B".equals(string)) strings.remove("B"); System.out.println(strings); 

如果我用LinkedList替换ArrayList ,我得到相同的结果。 但是,如果我将列表更改为("A", "B", "C", "D)或只是("A", "B")我会按预期得到exception。发生了什么?我正在使用jdk1.8.0_25如果相关的话。

编辑

我找到了以下链接

http://bugs.java.com/bugdatabase/view_bug.do?bug_id=4902078

相关部分是

天真的解决方案是在AbstractList中为hasNext添加编码检查,但这会使编纂检查的成本翻倍。 事实certificate,仅在最后一次迭代时进行测试就足够了,这几乎不会增加成本。 换句话说,hasNext的当前实现:

  public boolean hasNext() { return nextIndex() < size; } 

被此实现取代:

  public boolean hasNext() { if (cursor != size()) return true; checkForComodification(); return false; } 

由于Sun内部监管机构拒绝了此项更改,因此不会进行此更改。 正式裁决表明,这一变化“已certificate可能对现有代码产生重大的兼容性影响。” (“兼容性影响”是修复程序有可能用ConcurrentModificationException替换静默不当行为。)

作为一般规则,在检测到修改时抛出ConcurrentModificationException ,而不是引起修改。 如果您在修改后从未访问过迭代器,则不会抛出exception。 遗憾的是,这一细节使得ConcurrentModificationException对于检测数据结构的滥用非常不可靠,因为它们仅在损坏完成后被抛出。

此方案不会抛出ConcurrentModificationException因为修改后未在创建的迭代器上调用next()

for-each循环实际上是迭代器,所以你的代码实际上是这样的:

 List strings = new ArrayList<>(Arrays.asList("A", "B", "C")); Iterator iter = strings.iterator(); while(iter.hasNext()){ String string = iter.next(); if ("B".equals(string)) strings.remove("B"); } System.out.println(strings); 

考虑在您提供的列表上运行的代码。 迭代看起来像:

  1. hasNext()返回true,输入循环, – > iter移动到索引0,字符串=“A”,未删除
  2. hasNext()返回true,继续循环 – > iter移动到索引1,字符串=“B”,删除。 strings现在长度为2。
  3. hasNext()返回false(iter当前位于最后一个索引,不再有索引),退出循环。

因此,当对next()的调用检测到已经进行了修改时抛出了ConcurrentModificationException ,这种情况可以避免这种exception。

对于您的其他两个结果,我们会得到例外。 对于"A", "B", "C", "D" ,在删除“B”之后我们仍然在循环中,而next()检测到ConcurrentModificationException ,而对于"A", "B"我想象它是某种ArrayIndexOutOfBounds被捕获并作为ConcurrentModificationException重新抛出

ArrayList的迭代器中的hasNext就是

 public boolean hasNext() { return cursor != size; } 

remove调用之后,迭代器位于索引2,列表的大小为2,因此它报告迭代完成。 没有并发修改检查。 使用(“A”,“B”,“C”,“D”或(“A”,“B”),迭代器不在列表的新末尾,因此调用next ,并抛出exception。

ConcurrentModificationException只是一个调试辅助工具。 你不能依赖它们。

@Tavian Barnes完全正确。 如果有问题的并发修改未同步,则无法保证抛出此exception。 引用java.util.ConcurrentModification规范:

请注意,无法保证快速失败的行为,因为一般来说,在存在不同步的并发修改时,不可能做出任何硬性保证。 失败快速操作会尽最大努力抛出ConcurrentModificationException。 因此,编写依赖于此exception的程序以确保其正确性是错误的:ConcurrentModificationException应仅用于检测错误。

链接到JavaDoc以获取ConcurrentModificationException