for each循环不能改变集合结构的原理分析

本文详细解析了在Java中使用foreach循环遍历并同时删除集合元素时,为何会出现ConcurrentModificationException异常。深入探讨了迭代器的工作原理,解释了在遍历过程中修改集合结构导致异常的具体流程,并提供了正确的操作方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在使用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值,所以不会出现异常

 

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值