Java 中,时常遇到在循环中修改数据时,出现并发修改异常 ConcurrentModificationException。
异常成因
本质上,ConcurrentModificationException 用于在我们迭代的内容被修改时快速失败。让我们通过一个简单的测试来证明这一点:
public void cmException() {
List<Integer> integers = new ArrayList(1, 2, 3, 4);
for (Integer integer : integers) {
integers.remove(1);
}
}
在完成迭代之前,我们尝试删除一个元素,这会触发异常 ConcurrentModificationException。
异常处理
如果我们真的想在迭代时从集合中删除元素,可以有以下这些解决方案。
使用迭代器
原始的迭代器 Iterator 在删除元素时,不会导致 ConcurrentModificationException。
for (Iterator<Integer> iterator = integers.iterator(); iterator.hasNext();) {
Integer integer = iterator.next();
if(integer == 3) {
iterator.remove();
}
}
不过要注意,这里使用的是 iterator 的 remove 删除方法,而不是 List 的 remove 删除方法。
记录删除元素
迭代器 Iterator 在写法上有点冗长,我们可以采用先遍历在删除的方式实现:
List<Integer> list = new ArrayList(1, 2, 3, 4);
List<Integer> needRemove = new ArrayList();
for (Integer integer : list) {
if(integer == 3) {
toRemove.add(integer);
}
}
list.removeAll(toRemove);
使用 removeIf
Java 8 向Collection接口引入了removeIf()方法。这意味着如果我们使用它,我们可以使用函数式编程的思想再次达到相同的结果:
List<Integer> list = new ArrayList(1, 2, 3, 4);
list.removeIf(i -> i == 3);
使用 stream
stream 流可以方便地过滤出我们想要的数据:
List<Integer> list = new ArrayList(1, 2, 3, 4);
List<String> results = list
.stream()
.filter(i -> i != 2)
.map(Object::toString)
.collect(toList());
使用 stream 可以进行更多的操作,例如可以将 Integer 的集合变成 String 的集合,并且过滤掉了不需要的数据。