经常会有人这么对 list 进行遍历,错而不自知。
示例代码如下:
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("aaa");
list.add("bbb");
list.add("ccc");
list.add("ddd");
for (String str : list) {
if ("aaa".equals(str)) {
list.remove("aaa");
}
}
}
以上代码执行导致的报错信息如下:
Exception in thread "main" java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:901)
at java.util.ArrayList$Itr.next(ArrayList.java:851)
at demo.service.impl.test.main(test.java:14)
网上有很多博客对此都做了说明,这篇文章通过比较浅显易懂的方式说明报错产生的原因。
一、list.add
在 list.add 代码执行时,有一个变量发生改变了,那就是 modCount。在代码中 list.add 共执行4次,所以 modCount 的值为 4。
注:list 的 add()、remove() 和 clear() 都会改变 modCount 值。
二、for (String str : list)
for (String str : list) 调用的是 ArrayList 中内部类 Itr,Itr 是对 Iterator 的实现。而在 Iterator 开始前,会先执行 int expectedModCount = modCount
此时 expectedModCount 和 modCount 均为 4
三、list.remove("aaa")
在此处先看一下会报错的原因,以下是源码:
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
即 modCount 与 expectedModCount 不相等了,所以报错。
有人可能会跟我有一样的想法,为什么 list.remove("aaa") 时,不把 expectedModCount = modCount 重新赋值一次。其实是有的,只是调用的方法错了。
例子中 list.remove("aaa") 调用的 remove 源码如下:
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
fastRemove(index);
return true;
}
}
return false;
}
而使 modCount 的值改变的是其中的 fastRemove 方法。
fastRemove 源码如下:
private void fastRemove(int index) {
// 此处 modCount + 1
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
}
而真正使 expectedModCount = modCount 执行的源码如下:
public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();
try {
AbstractList.this.remove(lastRet);
if (lastRet < cursor)
cursor--;
lastRet = -1;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException e) {
throw new ConcurrentModificationException();
}
}
此代码在内部类 Itr 中。
这也就是为什么会说,如果 list 在循环中有删除操作,最好用 iterator 迭代的方式去做。
四、总结
简单总结一下
- list.remove() 没有对 expectedModCount 重新赋值
- iterator.remove() 对 expectedModCount 重新赋值
建议大家跟踪一下源代码,代码量不多,也很容易理解。
附录:内部类 Itr 源码(不长,分分钟看完)
private class Itr implements Iterator<E> {
/**
* Index of element to be returned by subsequent call to next.
*/
int cursor = 0;
/**
* Index of element returned by most recent call to next or
* previous. Reset to -1 if this element is deleted by a call
* to remove.
*/
int lastRet = -1;
/**
* The modCount value that the iterator believes that the backing
* List should have. If this expectation is violated, the iterator
* has detected concurrent modification.
*/
int expectedModCount = modCount;
public boolean hasNext() {
return cursor != size();
}
public E next() {
checkForComodification();
try {
int i = cursor;
E next = get(i);
lastRet = i;
cursor = i + 1;
return next;
} catch (IndexOutOfBoundsException e) {
checkForComodification();
throw new NoSuchElementException();
}
}
public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();
try {
AbstractList.this.remove(lastRet);
if (lastRet < cursor)
cursor--;
lastRet = -1;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException e) {
throw new ConcurrentModificationException();
}
}
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}