首先考虑下面代码的结果
ArrayList<Integer> list = new ArrayList<>();
for (int i = 0; i < 5; i++) {
list.add(i);
}
for (Integer integer : list) {
System.out.println(integer);
if(integer.equals(0)){
list.remove(integer);
}
}
System.out.println(list);
会报错
Exception in thread "main" java.util.ConcurrentModificationException
at java.base/java.util.ArrayList$Itr.checkForComodification(ArrayList.java:1043)
at java.base/java.util.ArrayList$Itr.next(ArrayList.java:997)
at Demo1.main(Demo1.java:13)
在来看这一段代码
ArrayList<Integer> list = new ArrayList<>();
for (int i = 0; i < 5; i++) {
list.add(i);
}
for (Integer integer : list) {
System.out.println(integer);
if(integer.equals(3)){
list.remove(integer);
}
}
System.out.println(list);
结果
0
1
2
3
[0, 1, 2, 4]
两端代码根本上没有什么区别,只是在删除的条件判断不太一样,那到底为什么会出现这样问题呢?
我们知道,对于集合,foreach 循环实际上是用的 iterator 迭代器迭代
即上面的代码部分反编译后就如下(对于反编译有神什么不懂的可以看我的上一篇博客Java中的语法糖):
for (Iterator<Integer> itr = list.iterator(); itr.hasNext();){
Integer num = (Integer)itr.next();
if(num.equals(0)){
list.remove(num);
}
}
那么我就来对源码进行分析
上源码(以下为ArrayList中的部分码)
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;
// prevent creating a synthetic constructor
Itr() {
}
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];
}
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 (ArrayList.this.modCount != this.expectedModCount) {
throw new ConcurrentModificationException();
}
}
首先先了解一下其中属性的含义
modCount— 在父类AbstractList中初始化为0,表示集合修改次数的期望值
cursor --迭代器的游标,元素的索引值,初始值为0
lastRet --返回最后一个元素的索引值、如果没有找到则返回-1
expectedModCount --修改次数的期望值,可以看到在迭代器初始化时,这个属性就被赋值为当前修改次数的值了。
checkForComodification --此方法用来检查修改次数是否发生变化,从而判断是否需要触发异常。
在迭代过程中,每一次迭代,cursor会+1, 而hasNext()会判断是否存在下一个元素、next()获取下一个元素的值,最终直到不存在下一个元素,则迭代结束。
我们看到报错的原因在于进入next中的 checkForComodification()报错了即:
ArrayList.this.modCount != this.expectedModCount
具体的原因是:如果你在遍历过程中删除元素,集合中modCount就会变化,但是迭代器中的expectedModCount没有改变所以报错了。
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;
}
/**
* Private remove method that skips bounds checking and does not
* return the value removed.
*/
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;
}
看如上的fastRemove中的modCount++;所以在一次进入next()中checkForComodification();会报错
为什么删除3不会报错呢?-----这就很巧妙了,原理是删除倒数第二个元素他就会进入hasNext(),自然就不会进入到next()了,在删除倒数第二个元素的场景下:当倒数第二个元素被删除完成,开始迭代最后一个元素时,此时cursor是4,size(表示集合长度的动态变化的量)由于在迭代过程倒数第二个元素移除了,所以-1, 此时cursor和size相等,不会再进入下一个迭代,因此不会触发checkForComodification方法产生异常,那也就意味着最后一个元素没有被遍历。
我们怎样来删除元素呢?----可以使用迭代器里面的remove()方法
public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();
try {
ArrayList.this.remove(lastRet);
cursor = lastRet;
lastRet = -1;
expectedModCount = modCount; // 处理expectedModCount
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
我们可以看见的 expectedModCount = modCount;也同时被修改了,所以就不会报错,看一下结果
ArrayList<Integer> list = new ArrayList<>();
for (int i = 0; i < 5; i++) {
list.add(i);
}
Iterator<Integer> iterator = list.iterator();
while(iterator.hasNext()){
System.out.println(iterator.next());
iterator.remove();
}
System.out.println(list);
}
结果
0
1
2
3
4
[]