在 Java 开发中,经常会遇到遍历集合并删除特定元素的需求。乍一看,这似乎是一个简单的操作,但如果不注意方式,很容易踩坑。这篇文章将带你理解如何在 Java 中安全地删除集合元素,并避免 ConcurrentModificationException
。
示例代码
假设我们有一个学生集合 students
,需求是遍历集合并删除 id
为 2
的学生,最初代码可能是这样的:
for (Student stu : students) {if (stu.getId() == 2)students.remove(stu);
}
问题:ConcurrentModificationException
运行上述代码时会抛出 ConcurrentModificationException
。这是因为 增强 for
循环(即 for-each
循环) 其实是依赖 隐式迭代器 实现的,当循环在迭代时直接修改集合,就会导致迭代器状态不一致,触发此异常。
为什么会出现异常?
在 for-each
循环中,集合会创建一个迭代器来遍历每个元素。然而,Java 的集合框架设计了修改检查机制,当迭代过程中发现集合被外部修改(如添加或删除元素)时,迭代器会抛出 ConcurrentModificationException
。这是为了确保集合的一致性并防止难以追踪的错误。
解决方案:正确的删除方法
为了避免这种异常,我们可以使用以下方法来安全地删除集合中的元素。
方法一:显式使用 Iterator
使用 Iterator
提供的 remove
方法来删除当前元素,这是最推荐的方法。Iterator
提供的 remove
操作是专门设计的,它能安全地删除当前迭代元素,不会导致并发修改异常。
代码示例如下:
Iterator<Student> iterator = students.iterator();
while (iterator.hasNext()) {Student stu = iterator.next();if (stu.getId() == 2) {iterator.remove(); // 安全删除当前元素}
}
在这个例子中,iterator.remove()
方法直接删除当前元素,是在迭代器层级上完成的,因此不会抛出 ConcurrentModificationException
。
方法二:使用 removeIf
(Java 8 及以上版本)
在 Java 8 及更高版本中,集合类引入了 removeIf
方法。它允许通过一个条件(如 Lambda 表达式)来批量删除满足条件的元素,非常简洁。
示例代码如下:
students.removeIf(stu -> stu.getId() == 2);
这行代码会遍历 students
集合,并删除所有 id
为 2
的学生,且不会产生 ConcurrentModificationException
。这是因为 removeIf
是由集合实现自身直接提供的,因此可以安全修改集合。
方法三:使用 Stream API
进行过滤
如果不在意是否修改原集合,而是希望获得过滤后的新集合,可以利用 Java 8 的 Stream API
。通过 filter
方法,我们可以得到一个不包含目标元素的新集合:
List<Student> filteredStudents = students.stream().filter(stu -> stu.getId() != 2).collect(Collectors.toList());
这里的 filter
方法不会修改原集合 students
,而是创建了一个符合条件的副本 filteredStudents
。如果业务逻辑允许创建新集合,这是一种非破坏性的好方法。
小结
在 Java 中遍历集合并删除元素时,避免 ConcurrentModificationException
的方法如下:
- 显式使用
Iterator
的remove
方法:适合在遍历中需要安全删除元素的场景。 - 使用
removeIf
方法:适合需要通过条件批量删除元素,语法简洁。 - 使用
Stream API
的filter
创建新集合:适合不修改原集合的情况下得到过滤结果。
完整示例代码
以下是完整的示例代码展示如何使用这些方法:
// 示例 1:使用 Iterator 的 remove 方法
Iterator<Student> iterator = students.iterator();
while (iterator.hasNext()) {Student stu = iterator.next();if (stu.getId() == 2) {iterator.remove();}
}// 示例 2:使用 removeIf 方法(Java 8 及以上)
students.removeIf(stu -> stu.getId() == 2);// 示例 3:使用 Stream API 生成新集合(不修改原集合)
List<Student> filteredStudents = students.stream().filter(stu -> stu.getId() != 2).collect(Collectors.toList());
结论
在 Java 中,直接在 for-each
循环中删除集合元素会导致 ConcurrentModificationException
。为了避免这个问题,优先使用 显式迭代器的 remove
方法,或 Java 8 引入的 removeIf
和 Stream API
。