在java1.8环境下,使用forEach的方式删除ArrayList中的目标元素,出现ConcurrentModificationException。
一段简单的代码:
List<String> strs = new ArrayList<String>(); // 1
strs.add("aaa"); // 2
strs.add("bbb"); // 3
strs.add("ccc"); // 4
//删除 "aaa"的元素
for (String item : strs) { // 5
if("aaa".equals(item)) { // 6
strs.remove(item); // 7
}
}
System.err.println("删除 'aaa'的元素后: "+ strs);
运行时出现异常:
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 codingrule.Demo_ArrayList_CME.main(Demo_ArrayList_CME.java:184)
为什么会出现这种异常呢?分析如下:
分析: 出现这个异常的原因是: expectedModCount != modCount。 那么为什么这两个变量不等呢?
该程序分为7个步骤,逐个分析。
Step 1. 创建一个ArrayList
源码:
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
这里创建了一个空的数组,作为集合中用于存储数据的容器。
Step 2. add("aaa"). 调用 ArrayList的 add(E e)方法
源码:
public boolean add(E e) {
// 这里重点说明要增加 modCount.那么modCount是什么东西,用来干什么的?
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
注意: modCount的声明是在AbstractList这个抽象类中,
protected transient int modCount = 0; // The number of times this list has been <i>structurally modified.
用来表示这个list被结构性地修改的次数。
查看ensureCapacityInternal(int minCapacity), 确实modCount被增加了。因此,每次add, modCount都会+1。
源码
private void ensureCapacityInternal(int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
因此到第4步结束时,modCount = 3
Step 5: for (String item : strs),
forEach的本质是:
<Step>1. 调用iterator(),创建一个Itr
public Iterator<E> iterator() {
return new Itr();
}
注意:当执行到这里时, expectedModCount = modCount
<Step>2. 调用 Itr内部的hasNext(),这个方法就相当于在遍历。
<Step>3. 调用 next()方法,将返回的元素赋值给item。这个方法也没对 modCount和expectedModCount做任何修改。只是调用了checkForComodification();
<p> Itr内部实现:
private class Itr implements Iterator<E> {
int cursor; // index of next element to return
int lastRet = -1; // index of last element returned; -1 if no such
int expectedModCount = modCount;
public boolean hasNext() {
return cursor != size;
}
@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];
}
Step 6. 判断是否相等
Step 7. 调用ArrayList的 remove(Object o), 方法内部判断传入的参数对象是否为null,匹配元素,找到目标下标 index,删除对应位置的元素。 如何删除? fastRemove(index)
源码:
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;
}
看看 fastRemove(int index)的内部实现:
注意:fastRemove(), 对modCount +1,此时 modCount = 4,而当程序再次执行到Step 5时,调用next()方法,该方法内部会调用checkForComodification()来检查modCount和ExpectedModCount是否相等,不相等就会抛出ConcurrentModificationException。此时modCount = 4, 而 expectedModCount = 3,因此抛出异常。
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
}
总结:
在使用forEach进行删除list的元素的时候会出现ConcurrentModificationException,因此使用 iterator的方式进行remove不会出错。
但是,为什么使用 iterator的方式进行remove就不会出错?
使用iterator进行迭代时,code如下(这里继续使用本例中的变量):
迭代部分
Iterator<String> it = strs.iterator();
while (it.hasNext()){
String item = it.next();
if ("aaa".equals(it)){
it.remove(); // <here>
}
}
其实,iterator方式和forEach方式的迭代步骤都是一样的:
<1> 调用iterator() new Itr();
<2> hasNext()
<3> next()返回当前元素值,赋值给 item
只有 remove的方式不一样: 前者remove时 会将modCount的赋值给 expectedModCount,所以不会出现异常而后者并没有这样做。
iterator的remove的方式为: 该方法是 ArrayList的内部私有类类 Itr类 的一个方法
public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();
try {
ArrayList.this.remove(lastRet);
cursor = lastRet;
lastRet = -1;
expectedModCount = modCount; //<NOTE> 重点在这里
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
分析不到位的地方,请各位大佬指出,多谢。