Iterator.remove():遍历集合时安全删除元素的唯一正确方法
在 Java 开发过程中,许多开发者都曾遇到过这样一个典型问题:在遍历集合时尝试删除元素,却意外引发了 ConcurrentModificationException 异常。无论是直接调用集合自身的 remove() 方法,还是在简洁的 for-each 循环中进行删除操作,都会立即触发此异常。那么,如何才能在遍历时安全地移除元素呢?标准答案是:使用 Iterator.remove() 方法。这是 Java 语言设计者为单线程遍历过程中进行结构性修改所提供的唯一安全机制。

为什么必须使用 Iterator.remove()?
要理解这一方法的必要性,需要了解 Java 集合框架的内部工作原理。诸如 ArrayList、HashSet 等常见集合类,内部都维护着一个名为 modCount 的“修改计数器”。每当集合的结构发生改变(例如添加或删除元素),该计数器的值就会递增。而当你创建一个迭代器时,它会将当前的 modCount 值记录下来,保存为 expectedModCount。
关键在于:每次调用 iterator.next() 方法获取下一个元素之前,迭代器都会执行一次“一致性检查”,比对集合当前的 modCount 与自身记录的 expectedModCount 是否相等。如果发现两者不一致——例如在循环中直接调用了 list.remove(item),导致集合的 modCount 发生变化而迭代器并未感知——迭代器便会立即抛出 ConcurrentModificationException 异常,以防止后续操作可能引发的数据不一致问题。
而 Iterator.remove() 方法之所以能够成为例外,是因为它实现了“内外协同”:该方法由迭代器对象自身执行删除操作,并且在成功删除元素后,会同步更新其内部的 expectedModCount 值,使其与集合最新的 modCount 保持一致。这样,后续的一致性检查就能顺利通过,遍历过程得以安全地继续进行。
掌握 Iterator.remove() 的正确操作步骤
使用该方法时,必须遵循两条核心原则:必须先调用 next() 再调用 remove(),并且在单次循环中 remove() 最多只能被调用一次。违反其中任何一条,都会导致 IllegalStateException 异常。
具体的操作流程可以总结为以下几个步骤:
- 获取迭代器:首先,通过调用集合的
iterator()方法获取对应的迭代器对象。 - 循环遍历:使用
while (it.hasNext())作为循环条件,确保在有效的遍历范围内进行操作。 - 定位与判断:在循环体内,必须首先通过
it.next()获取当前指向的元素,然后根据具体的业务逻辑判断该元素是否需要被删除。 - 执行删除:如果元素满足删除条件,则调用
it.remove()。此操作将删除上一次next()方法所返回的那个元素。
需要特别注意:在调用一次 remove() 方法之后,必须再次调用 next() 方法将迭代器移动到下一个元素,才能执行下一次删除操作。连续调用两次 remove(),或者在调用 next() 之前就调用 remove(),都会触发异常。
从错误到正确:代码示例对比
假设我们需要从一个字符串列表中删除所有长度大于 5 的元素。以下是一个典型的错误示例,它使用了 for-each 循环:
for (String s : list) {
if (s.length() > 5) {
list.remove(s); // ❌ 会立即抛出 ConcurrentModificationException 异常
}
}
这段代码虽然看起来简洁,但一旦运行就会导致程序崩溃。因为 for-each 循环在底层同样依赖于迭代器,直接调用 list.remove(s) 破坏了迭代器与集合之间约定的状态一致性。
正确的做法是使用迭代器进行显式操作:
Iteratorit = list.iterator(); while (it.hasNext()) { String s = it.next(); // 首先定位到当前元素 if (s.length() > 5) { it.remove(); // ✅ 通过迭代器安全地删除元素 } }
这才是确保万无一失的写法。迭代器明确知晓当前正在操作的元素,并能妥善处理其内部状态的同步更新。
现代替代方案与关键注意事项
当然,如果你使用的是 Java 8 或更高版本,有一种更为优雅的实现方式。集合框架提供了 removeIf(Predicate filter) 方法,仅用一行代码即可完成条件过滤与删除:
list.removeIf(s -> s.length() > 5); // ✅ 代码清晰且安全
此方法在底层本质上仍然是基于迭代器模式实现的,只是语法糖让我们编写起来更加便捷。对于简单的条件删除场景,这无疑是首选方案。
不过,仍有几点需要特别注意:
- 避免混用:不要在代码中既使用
removeIf()方法,又在另一个循环中手动使用iterator.remove(),这会使代码逻辑变得难以理解和维护。 - 线程安全并非其职责:
Iterator.remove()方法解决的是单线程遍历过程中的修改异常问题,它并不提供任何线程安全的保证。如果集合会被多个线程同时访问和修改,你必须寻求更根本的解决方案,例如使用CopyOnWriteArrayList这类线程安全的集合类,或者在操作时进行恰当的同步控制与加锁。
归根结底,理解 Iterator.remove() 的工作原理,不仅是掌握一个 API 的用法,更是深入理解 Java 集合框架“快速失败”(fail-fast)设计思想的关键。在合适的场景中运用它,能够使你的代码更加健壮与清晰。
