Java-集合操作常见的ConcurrentModificationException

用增强循环操作集合元素时通常都会抛出ConcurrentModificationException

普通for循环删除指定元素

1
2
3
4
5
6
7
8
9
10
11
12
13
14
List<String> userNames = new ArrayList<String>() {{
add("Hollis");
add("hollis");
add("HollisChuang");
add("H");
}};

for (int i = 0; i < userNames.size(); i++) {
if(userNames.get(i).equalsIgnoreCase("hollis")){
userNames.remove(i);
}
}

System.out.println(userNames);
1
[hollis, HollisChuang, H]

只删除了第一个元素,原因比较简单:删除了元素涉及元素移动,i++跳过了后面的元素
解决办法是倒着遍历或者使用迭代器遍历

增强for循环删除元素

1
2
3
4
5
for(String name : userNames){
if(name.equalsIgnoreCase("hollis")){
userNames.remove(name);
}
}
1
2
3
4
Exception in thread "main" java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:909)
at java.util.ArrayList$Itr.next(ArrayList.java:859)
at com.raycloud.report.collection.controller.MainTest.main(MainTest.java:27)

在增强for循环中使用add方法添加元素,结果也会同样抛出该异常
之所以会出现这个异常,是因为触发了一个Java集合的错误检测机制——fail-fast

对增强for循环语法糖进行解糖

1
2
3
4
5
6
7
8
Iterator var2 = userNames.iterator();

while(var2.hasNext()) {
String name = (String)var2.next();
if (name.equalsIgnoreCase("hollis")) {
userNames.remove(name);
}
}

反编译可看出其是由迭代器遍历实现
直接运行解糖的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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];
}
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}

可知next方法会校验这两个值,这两个值不相等时就会抛出并发修改异常

modCount是ArrayList中的一个成员变量。它表示该集合实际被修改的次数。调用集合的add和remove函数都会使该值加1

expectedModCount 是 ArrayList中的一个内部类——Itr中的成员变量。expectedModCount表示这个迭代器期望该集合被修改的次数。其值是在ArrayList.iterator方法被调用的时候初始化的。只有通过迭代器对集合进行操作,该值才会改变

上图中remove使用的是userNames.remove(name)集合的remove函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public E remove(int index) {
rangeCheck(index);

modCount++;//+1
E oldValue = elementData(index);

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

return oldValue;
}

集合的remove只操作了modCount值,expectedModCount并没有改变,所以在下一次next时就会不一致,抛出并发修改异常

解决办法

  1. 使用迭代器的remove函数
    1
    2
    3
    4
    5
    6
    7
    Iterator iterator = userNames.iterator();

    while (iterator.hasNext()) {
    if (iterator.next().equals("Hollis")) {
    iterator.remove();
    }
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();

try {
ArrayList.this.remove(lastRet);
cursor = lastRet;
lastRet = -1;
expectedModCount = modCount;//迭代器的remove会使两个值相等
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
  1. 使用Java 8中提供的filter过滤

    1
    userNames = userNames.stream().filter(userName -> !userName.equals("Hollis")).collect(Collectors.toList());
  2. 使用增强for循环也可以,remove后直接退出,也就不会再次调用next方法

    1
    2
    3
    4
    5
    6
    for (String userName : userNames) {
    if (userName.equals("Hollis")) {
    userNames.remove(userName);
    break;
    }
    }

使用迭代器怎么添加元素

Iterator迭代器没有添加功能,可使用其子接口ListIterator

1
2
3
4
5
6
7
8
9
10
ListIterator var2 = userNames.listIterator();

while(var2.hasNext()) {
String name = (String)var2.next();
if (name.equalsIgnoreCase("hollis")) {
var2.add("cccc");
}
}

System.out.println(userNames);

1
[Hollis, cccc, hollis, cccc, HollisChuang, H]
Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×