在使用for each循环时,有一种常用的错误方式,就是在循环的时候调用集合的remove/add方法
例如:
for (Integer a : list){
if (a == 100){
list.remove(a);
}
}
如果这么使用,代码在运行时会抛出
java.util.ConcurrentModificationException
发生这种异常的原因可以用一句话概括:迭代器的内部维护了一些索引相关的数据,要求在迭代过程中,容器不能发生结构性变化,否则这些索引位置失效。结构性变化是指,添加和删除元素,不包括修改元素。
原理分析
for each实际上是java语言的一个语法糖,在使用时,实际上编译器会将它转换为迭代器遍历,
例如
for (String s : list){
System.out.println(s);
}
编译器将它转换为
Iterator<String> iterator = inlist.iterator();
while (iterator.hasNext()){
System.out.println(iterator.next());
}
以ArrayLIst为例,ArrayList的iterator代码如下所示
public Iterator<E> iterator() {
return new Itr();
}
Itr()对象是一个成员内部类,实现了Iterator接口,其中有三个实例成员变量,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;
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();
}
}
@Override
@SuppressWarnings("unchecked")
public void forEachRemaining(Consumer<? super E> consumer) {
Objects.requireNonNull(consumer);
final int size = ArrayList.this.size;
int i = cursor;
if (i >= size) {
return;
}
final Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length) {
throw new ConcurrentModificationException();
}
while (i != size && modCount == expectedModCount) {
consumer.accept((E) elementData[i++]);
}
// update once at end of iteration to reduce heap write traffic
cursor = i;
lastRet = i - 1;
checkForComodification();
}
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
简要介绍一个这三个成员变量
- cursor 下一个要返回的元素位置
- lastRet 最后一个返回的索引位置,如果没有,则为-1
- expectedModCount表示期望的修改次数,初始化为modCount的值
下面阐述一下修改集合结构出现异常的流程
1 在调用修改集合结构的方法(如add/remove方法)时,会修改modcount变量的值
public E remove(int index) {
rangeCheck(index);
modCount++; // 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;
}
2 在修改完结构时,会继续遍历集合,调用迭代器的next方法
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];
}
3 首先 next方法调用了checkForComodification() 它的代码为
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
因为调用了remove方法 modcount方法发生了改变,但是expectedModCount方法没有同步进行改变,所以当调用到了checkForComodification方法时,判断这两个变量发现了不一致,所以直接抛出了异常
如何避免异常
可以调用迭代器自己的remove方法
while (iterator.hasNext()){
if (iterator.next() == 100) {
iterator.remove();
}
}
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();
}
}
Itr 的remove方法 会同步expectedModCount和modCount值,所以不会出现异常