为什么不能在foreach 循环里进行元素的 remove/add 操作

415 阅读2分钟

如果列表在任何时间从结构上修改创建迭代器之后,以任何方式除非通过迭代器自身remove/add方法,迭代器都将抛出一个ConcurrentModificationException,这就是单线程状态下产生的 fail-fast 机制

fail-fast 机制:即快速失败机制,是java集合(Collection)中的一种错误检测机制。当在迭代集合的过程中该集合在结构上发生改变的时候,就有可能会发生fail-fast,即抛出 ConcurrentModificationException异常。fail-fast机制并不保证在不同步的修改下一定会抛出异常,它只是尽最大努力去抛出,所以这种机制一般仅用于检测bug。

Java8开始,可以使用Collection#removeIf()方法删除满足特定条件的元素,如

List<Integer> list = new ArrayList<>();
for (int i = 1; i <= 10; ++i) {
    list.add(i);
}
list.removeIf(filter -> filter % 2 == 0); /* 删除list中的所有偶数 */
System.out.println(list); /* [1, 3, 5, 7, 9] */Copy to clipboardErrorCopied

java.util包下面的所有的集合类都是fail-fast的,而java.util.concurrent包下面的所有的类都是fail-safe的。

不要在 foreach 循环里进行元素的 remove/add 操作 第一种不会抛出异常,而第二种方式会抛出ConcurrentModificationException异常。 对第二种代码进行反编译,得出的是 void testFastFail() { List list = new ArrayList(); list.add("1"); list.add("2"); Iterator var2 = list.iterator();

while(var2.hasNext()) {
    String s = (String)var2.next();
    if (s.equals("2")) {
        list.remove(s);
    }
}

打入断点,查看其关键方法

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];
}
final void checkForComodification() {
    if (modCount != expectedModCount)
        throw new ConcurrentModificationException();
}

cursor为当前指向的元素。size为当前列表元素个数。expectedModCount为记录修改次数。即先用hasNext()判断是否还有下一个元素,有就执行next(),然后判断modCount 是否等于expectedModCount,等于就继续执行,对if语句进行判断,执行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(index);
                return true;
            }
    }
    return false;
}
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
}

这里会执行fastRemove方法,此时size--,modCount++,由于没有重新校正expectedModCount,故在checkForComodification方法中会抛出异常。

而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();
    }
}

会重新矫正expectedModCount的值,故不会报错。

参考 snailclimb.gitee.io/javaguide/#…