Iterator.remove()由于同步更新expectedModCount以绕过modCount检查,是唯一安全的遍历删除方法;removeIf()Java 8+推荐的批量条件删除方案,底层基于Iterator.remove()但更简单;倒序for循环虽然可以避免异常,但不推荐,因为可读性差,不适合set/map,没有性能优势;并发场景下,需要安全集合或外锁Copyonwritearylist等线程。 用 Iterator.remove() 删除遍历中唯一安全的方法
在增强 for 循环(for (T item : list))或普通 for 循环中直接调用 list.remove(item) 或 list.remove(i),触发几乎是不可避免的 ConcurrentModificationException。这是因为 ArrayList、LinkedList 等待非线程安全集合内部维护 modCount 计数器,当迭代器检测到计数器意外修改时,会抛出异常。
正确的方法是使用迭代器本身 remove() 方法-它会同步更新 expectedModCount,从而绕过检查: Iterator it = list.iterator(); while (it.hasNext()) { String s = it.next(); if (s.startsWith("temp")) { it.remove(); // ✅ 安全删除 } } removeIf() 适合 Java 8+ 删除批量条件
Collection.removeIf(Predicate) 它是一种更简单、语义更清晰的替代方案,底层仍然是基于 Iterator.remove(),但是包装了循环逻辑。适用于需要根据条件批量删除元素的场景,对于 ArrayList、HashSet、LinkedHashSet 均有效。 不能用于只读集合(例如) Collections.unmodifiableList()),会抛 UnsupportedOperationException 对 CopyOnWriteArrayList 也支持,但注意线程安全,每次修改都复制数组,不适合高频写作场景 避免在 Predicate 中等修改集合本身(例如 lambda 里再调用 remove()),这将导致行为未定义 list.removeIf(s -> s == null || s.trim().isEmpty()); 普通 for 循环倒序遍历可以避免异常,但不推荐
然后用索引删除(for (int i = list.size()-1; i >= 0; i--))真的不会触发 ConcurrentModificationException,因为没有迭代器,所以不依赖它 modCount 检查。但是它有明显的缺陷:
逻辑反直觉,容易出错(比如错过 i-- 或边界写成 > 0) 删除后,索引会自动向前移动。如果正序遍历时跳过下一个元素的问题,虽然倒序下没有出现,但代码可读性差 仅适用于 List 实现,对 Set 或 Map 不适用 没有性能优势:ArrayList.remove(i) 后续元素仍需移动;LinkedList 则因不支持 O(1) 随机访问速度较慢 并发场景下的硬套单线程方案
若多个线程可能同时读写集合,Iterator.remove() 和 removeIf() 还是会有问题——它们不是原子操作,迭代过程本身也不锁定。 线程安全替代品优先:CopyOnWriteArrayList(适合读多写少)、ConcurrentHashMap(对应 Map 场景) 若必须用 ArrayList,需要外层加锁(如 synchronized(list)),但此时要确保所有访问(包括遍历、增删)都在同一把锁下,否则无效 Vector 和 Stack 虽然方法同步,但迭代器仍然不能保证安全,不建议使用新项目 真正容易被忽视的是:即使用 Iterator.remove(),只要在迭代过程中修改了其他线程的集合,就会出现异常——安全的前提是“不移交单线程控制权”。