背景
如下代码
java
public static void main(String[] args) {
ArrayList<String> l = new ArrayList<>();
l.add("1");
for (String s : l) {
l.remove(s);
}
}
运行代码会报以下错误:
问题:在for循环或forEach中,对ArrayList进行增删操作,会报错ConcurrentModificationException
,而倘若使用Iterator
进行增删,则不会报错,究竟是何缘故?跟着Arvin来研究一下
原因
根据之前在《代码大全》和《程序员修炼之道:通向务实的最高境界》两书中,均有关于调试的技巧,先冷静,再仔细看报错的内容,其实可以看到这里的堆栈很浅,只有简单的几行调用,那么此时看一下是什么地方抛出的该异常
这里可以看到,,同时在ArrayList中调用iterator()
方法时,会返回一个Itr
对象,而Itr
类实现了迭代器接口,而从上面的堆栈可以看出,是在调用迭代器的next()
方法时,方法内部调用checkForComodification()
方法时报出的该错误
这个方法里面,会去检查modCount
和expectedModCount
的值是否一致,modCount
顾名思义,就是修改次数,该字段是ArrayList
的父类AbstractList
的字段
java
/**
* The number of times this list has been <i>structurally modified</i>.
* Structural modifications are those that change the size of the
* list, or otherwise perturb it in such a fashion that iterations in
* progress may yield incorrect results.
*
* <p>This field is used by ...
*/
protected transient int modCount = 0;
这个上面说了一大段话都不重要,简单理解就是对里面元素进行增删时,这个参数值会发生变化
那到了这里,就可以看一下在for循环中调用的remove()
方法了,
java
public boolean remove(Object o) {
final Object[] es = elementData;
final int size = this.size;
int i = 0;
found: {
if (o == null) {
for (; i < size; i++)
if (es[i] == null)
break found;
} else {
for (; i < size; i++)
if (o.equals(es[i]))
break found;
}
return false;
}
fastRemove(es, i);
return true;
}
这里面逻辑不走赘述,就只是判断要删除的元素如果是null,就找到第一个null元素进行删除,否则就找到相同的元素进行删除,之后再执行fastRomve()
方法,那再来看这个方法
java
private void fastRemove(Object[] es, int i) {
modCount++;
final int newSize;
if ((newSize = size - 1) > i)
System.*arraycopy*(es, i + 1, es, i, newSize - i);
es[size = newSize] = null;
}
这里多的也不需要看,只需关注modCount++
这个操作,删除之前,对修改次数进行了变更,而上面next()
方法中调用checkForComodification()
方法,会去检查modCount
和expectedModCount
的值是否一致,很明显是完全不一致的,所以就报了这个错
综上,报错的原因,也就找到了
解决方案
- 如果遍历删除元素,采用迭代器的方式去删除,具体原因,浅析一下迭代器中删除的代码
java
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();
}
}
这段代码也很简单,只需关注两个点,一个是在这里面仍然去调用了ArrayList
的remove()
方法,另外,就是expectedModCount = modCount;
操作,这样,再去迭代的时候,就不会报错两个值不一致了
- 假如只是普通的元素删除,无需遍历,仍然可以使用
ArrayList
的remove()
方法 - 选用其他的数据结构,例如
CopyOnWriteArrayList
,但需注意,一定要自己去阅读一下源码,比如这个里面,就不支持在迭代器中删除元素
java
/**
* Not supported. Always throws UnsupportedOperationException.
* @throws UnsupportedOperationException always; {@code remove}
* is not supported by this iterator.
*/
public void remove() {
throw new UnsupportedOperationException();
}