概述
对java.util包下的集合类型来说,如果在通过for-each循环进行遍历时,对集合进行修改操作(删除、添加、修改元素),很多情况下会抛出ConcurrentModificationException异常[1]。这是因为for-each循环是通过迭代器的方式进行的遍历。而该包下的迭代器都属于fail-fast迭代器[2],即不允许在遍历的同时,对集合进行修改,因为这样会导致不确定的遍历结果。例如:
代码语言:java复制List<String> list = new ArrayList<>();
list.add("a");
list.add("b");
list.add("c");
for (String s : list) {
if ("c".equals(s)) {
list.remove(s);
}
}
原理
集合内部有一个modCount属性,用于记录集合被修改的次数,而迭代器作为集合的内部类,有一个expectedModCount属性。每次迭代时都会实例化一个迭代器,实例化时expectedModCount等于modCount. 如果在迭代进行中,调用了集合的remove、add等方法,modCount就会增加,而迭代器中的expectedModCount仍然保持不变。而迭代中每次通过next方法获取下一个元素时,都会检查这两个值是否相等,如不相等就会抛出ConcurrentModificationException.
解决方案
不推荐在遍历的同时对集合进行修改,可新建一个集合,用于保存修改,而不是直接在原集合上修改。但若是出于代码简洁的目的,想要实现在遍历时删除某个元素,可通过以下方式实现:
1. 通过迭代器删除元素
代码语言:java复制Iterator<String> itr = list.iterator();
while (itr.hasNext()) {
String s = itr.next();
if ("c".equals(s)) {
itr.remove();
}
}
2. 通过stream删除元素
代码语言:java复制List<String> list = list.stream().filter(s -> !"c".equals(s)).collect(Collectors.toList());
3. 通过removeIf方法删除元素
代码语言:java复制list.removeIf(s -> "c".equals(s)); //语法糖,removeIf本质是通过迭代器进行删除
[1]: 即便不抛异常,也无法保证遍历结果的准确性
[2]: fail-fast是一种通用的设计思想,指一旦检测到可能发生错误,就马上抛出异常,并终止程序执行
参考文献
https://www.baeldung.com/java-fail-safe-vs-fail-fast-iterator
https://developer.aliyun.com/article/1009062