使用iterator.remove()仍抛出java.util.ConcurrentModificationException的原因与解决
首先直接给结论:不是迭代器“未感知”新增元素,而是它故意检测到这种外部修改就抛出异常。
ArrayList的迭代器属于「快速失败(fail-fast)」迭代器:当你创建迭代器后,它会记录当前集合的修改次数modCount,并把这个值存在自己的expectedModCount里。在迭代过程中,每次调用next()都会检查这两个值是否一致——如果不一致,就说明集合的结构(添加、删除元素)被迭代器以外的方式修改了(比如你直接调用list.add("6")),这时候迭代器就会抛出ConcurrentModificationException,防止因为并发修改导致的不确定遍历结果(哪怕你是单线程操作也会触发这个机制)。
而iterator.remove()之所以安全,是因为这个方法会在删除元素后同步更新expectedModCount,让它和集合的modCount保持一致。但list.add()不会做这个同步,所以迭代器下次检查时就会发现值不匹配,抛出异常。
接下来给你几个可行的解决方案:
方案1:使用ListIterator的add()方法(推荐,单线程场景最适配)
ListIterator是普通Iterator的子类,专门为List集合设计,它支持在迭代过程中安全添加、修改元素——因为listIterator.add()会同步更新expectedModCount,让迭代器感知到这次修改。示例代码:
public static void main(String args[]) { List<String> list = new ArrayList<>(); list.add("1"); list.add("2"); list.add("3"); list.add("4"); list.add("5"); // 用ListIterator替代普通Iterator ListIterator<String> it = list.listIterator(); while (it.hasNext()) { String value = it.next(); // 假设在某个条件下添加元素"6" if ("3".equals(value)) { it.add("6"); } } System.out.println(list); // 输出: [1, 2, 3, 6, 4, 5] }
方案2:先收集要添加的元素,迭代结束后批量添加
如果不想用ListIterator,可以先把需要添加的元素存到一个临时集合里,等迭代完全结束后,再一次性把临时集合的元素添加到原集合中。这样就不会在迭代过程中修改原集合的结构,自然不会触发异常:
public static void main(String args[]) { List<String> list = new ArrayList<>(); list.add("1"); list.add("2"); list.add("3"); list.add("4"); list.add("5"); List<String> toAddElements = new ArrayList<>(); for (Iterator<String> it = list.iterator(); it.hasNext();) { String value = it.next(); if ("3".equals(value)) { toAddElements.add("6"); } // 这里依然可以安全使用it.remove()删除元素 } // 迭代结束后批量添加 list.addAll(toAddElements); System.out.println(list); // 输出: [1, 2, 3, 4, 5, 6] }
方案3:使用CopyOnWriteArrayList(适合读多写少的并发场景)
如果是在多线程环境下,或者你希望迭代过程中允许直接修改集合但不抛异常,可以用java.util.concurrent.CopyOnWriteArrayList。它的迭代器是基于集合的快照创建的,迭代过程中不会检测集合的修改,不过修改操作(add/remove)会复制整个底层数组,所以适合读多写少的场景:
import java.util.concurrent.CopyOnWriteArrayList; import java.util.Iterator; public static void main(String args[]) { CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>(); list.add("1"); list.add("2"); list.add("3"); list.add("4"); list.add("5"); Iterator<String> it = list.iterator(); while (it.hasNext()) { String value = it.next(); if ("3".equals(value)) { list.add("6"); // 直接调用add不会抛异常 } } System.out.println(list); // 输出: [1, 2, 3, 4, 5, 6] // 注意:迭代器遍历的是创建时的快照,所以迭代过程中看不到新增的"6" }
内容的提问来源于stack exchange,提问作者IspellColourWithAU

