本文已参与「新人创作礼」活动,一起开启掘金创作之路。
使用增强for循环、forEach循环、迭代器调用List自身的remove()会抛ConcurrentModificationException异常,正确操作案例如下:
例如:将状态为DEL的用户从List中删除。 userList为原始List数据,返回操作后的数据。
解决方案:
方法一:使用迭代器的Iterator.remove()删除
List<User> userList = userService.getUserList();
Iterator<User> iterator = userList.iterator();
while (iterator.hasNext()) {
User user = iterator.next();
//将status为DEL的删除
if (StatusEnum.DEL.equals(user.getStatus())) {
//userList.remove(user); //ConcurrentModificationException
iterator.remove();
}
}
return userList;
方法二:使用stream过滤(JDK8+)
List<User> userList = userService.getUserList();
List<User> list = userList.stream().filter(user -> !StatusEnum.DEL.equals(user.getStatus())).collect(Collectors.toList());
return list;
方法三:使用逆序for循环调用List.remove(Object)删除
正序for循环需要注意索引问题。
List<User> userList = userService.getUserList();
for (int i = userList.size() - 1; i >= 0; i--) {
User user = userList.get(i);
//将status为DEL的删除
if (StatusEnum.DEL.equals(user.getStatus())) {
userList.remove(user);
}
}
return userList;
方法四:使用Collection的removeIf()删除(JDK8+)
Collection<User> userList = userService.getUserList();
//将status为DEL的删除
userList.removeIf(user -> StatusEnum.DEL.equals(user.getStatus()));
return Lists.newArrayList(userList);
D: \Soft\jdk1.8.271\bin\java.exe . . .
java.util.ConcurrentModificationException <Create breakpoint>
at java.util.ArrayList$Itr.checkForComodification (ArrayList.java:911)
at java.util.ArrayList$Itr.next(ArrayList.java:861)
at AppTest.testList(AppTest.java:347) <22 internal calls>
为什么会抛异常?
对于List自身的remove()方法,通过异常信息可以看到异常信息抛出是在java.util.ArrayList$Itr.checkForComodification()方法,对应remove方法源码如下,在删除时做了modCount++操作,接下来next()方法开始就调用了checkForComodification()校验modCount与expectedModCount是否相等,不相等就会抛出异常。
expectedModCount:是ArrayList修改次数的初始值,它的初始值为modCount=0。 modCount:是AbstractList类中的一个成员变量,该值表示对List的修改次数,每次调用add()或者remove()就会对modCount进行加1操作。
public boolean remove(Object o) {
if (o == null) {
for (int index = 0; index < size; index++)
if (elementData[index] == null) {
fastRemove(index);
return true;
}
} else {
for (int index = 0; index < size; index++)
if (o.equals(elementData[index])) {
fastRemove(index);
return true;
}
}
return false;
}
private void fastRemove(int index) {
modCount++;
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index, numMoved);
elementData[--size] = null; // clear to let GC do its work
}
private class Itr implements Iterator<E> {
@SuppressWarnings("unchecked")
public E next() {
checkForComodification();
int i = cursor;
if (i >= size) throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length) throw new ConcurrentModificationException();
cursor = i + 1;
return (E) elementData[lastRet = i];
}
public void remove() {
if (lastRet < 0) throw new IllegalStateException();
checkForComodification();
try {
ArrayList.this.remove(lastRet);
cursor = lastRet;
lastRet = -1;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
通过源码我们会发现List内部有一个Itr迭代器也提供了remove()方法,这个方法在删除后做了一个操作expectedModCount = modCount;,所以只要调用迭代器的remove()方法或重新刷新list再删除就不会有问题。