List在foreach循环的时候remove(ConcurrentModificationException)

通过这个小例子了解几个知识:

(1)ArrayList的内部Iterator理解
(2)modCount的用途
(3)为什么会ConcurrentModificationException以及原理
(4)有什么办法实现遍历中remove

代码例子:

在这里插入图片描述

运行结果:

在这里插入图片描述

查看生成的class文件:

在这里插入图片描述

(1)ArrayList的内部Iterator理解:
其实foreach语法糖在实际执行的时候是通过迭代器(Iterator)实现的,
主要用了上图的hasNext()和next()
看一下ArrayList的Iterator。

在这里插入图片描述
返回了内部类Itr
下面是itr 源码:

/**
 * An optimized version of AbstractList.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];
    }

    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:表示下一个要访问的元素的索引,从next()和hasNext方法的具体实现都可以看出。
在这里插入图片描述 lastRet:表示上一个访问的元素的索引
expectedModCount:表示对ArrayList修改次数的期望值,它的初始值为modCount。
modCount是AbstractList类中的一个成员变量:他代表着list被修改的次数初始值为0

protected transient int modCount = 0;

该值表示对List的修改次数,add()、remove()、addAll()、removeRange()
及clear()方法。这些方法每调用一次,modCount的值就加1。注:add()及
addAll()方法的modCount的值是在其中调用的ensureCapacity()方法中增
加的。

(2)modCount的用途(为什么需要它):
	在java的集合类中常见的变量modCount,用于记录对象的修改次数,
比如增、删、改,也基本存在于非线程安全的集合类中
	有一点版本控制的意思,可以理解成version,在特定的操作下需要
对version进行检查,适用于Fail-Fast机制。
	在java的集合类中存在一种Fail-Fast的错误检测机制,当多个线程对
同一集合的内容进行操作时,可能就会产生此类异常。
	比如当A通过iterator去遍历某集合的过程中,其他线程修改了此集合
,此时会抛出ConcurrentModificationException异常。
	此类机制就是通过modCount实现的,在迭代器初始化时,会赋值
expectedModCount,在迭代过程中判断modCount和expectedModCount、
是否一致;
(3)为什么会ConcurrentModificationException以及原理:
通过对上面基础信息的理解,下面来一点一点走,寻找问题
上面程序:

在这里插入图片描述第一步(图中圈1)
当调用platformList.iterator()返回一个Iterator

第二步(图中圈2)
通过Iterator的hashNext()方法判断是否还有元素未被访问

关于hasNext()方法的实现:
在这里插入图片描述第三步(图中圈3)
调用next()方法获取,看一下next的实现
在这里插入图片描述进来首先调用的这个方法
在这里插入图片描述核心逻辑就是比较modCount和expectedModCount这两个变量的值

Debug看一下全流程:
通过三次add
在这里插入图片描述由于调用了三次add所以list的Size是3,同时modCount也是3
在这里插入图片描述所以此时在执行圈1的时候,由下图可知expectedModCount也是等于3:
在这里插入图片描述由于第一次进入循环此时expectedModCount==modCount
所以
在这里插入图片描述这个校验是没有问题的——通过下图圈1校验;
在这里插入图片描述然后next()这个方法中还把cursor(当前元素索引)赋值给lastRet(前一个元素索引):圈3,然后增加当前元素索引:圈2,并且返回当前的元素:圈4。

经过上面一顿操作此时
在这里插入图片描述第四步(图中圈4)执行remove

从.Class文件点进去。发现调用的remove是ArrayList下面的remove,并不是迭代器的remove或者说并不是内部类Itr中的remove(Itr实现Iterator)
在这里插入图片描述调用的是这个:而这个调用了fastRemove
在这里插入图片描述fastRemove中对modCount进行了加一操作而此时注意expectedModCount是没有变的,此时两个值:
modCount:4
expectedModCount:3

此时执行完第一次循环:
在这里插入图片描述当进入第二次循环的时候,再次调用next方法的时候,还是会校验这两个值:
在这里插入图片描述但是此时两个值已经不相等:所以抛出异常:ConcurrentModificationException

(4)有什么办法实现遍历中remove(极不推荐,很危险,多机环境或者高并发场景都可能出问题,并且阿里开发规范明确要求禁止在遍历时删除元素,正确的做法是遍历的时候标记出来,后续代码再用也方便)

1、使用Iterator的remove()方法
在这里插入图片描述现在想一想 为啥迭代器的remove就没事呢:看下源码
在这里插入图片描述圈1的地方调用了ArrayList里面的remove,想一下上面的操作。ArrayList的remove仅仅是增加了modCount,没有增加expectedModCount,然后导致异常,因此在这里调用完这个ArrayList的remove,在主动同步一下expectedModCount:上图圈2,这样使得modCount==expectedModCount再次进入循环的时候调用检查方法就不会异常了:
在这里插入图片描述2、使用for循环正序遍历

public static void main2(String[] args) {
    List<String> platformList = new ArrayList<>();
    platformList.add("博客园");
    platformList.add("CSDN");
    platformList.add("掘金");

    for (int i = 0; i < platformList.size(); i++) {
        String item = platformList.get(i);
        if (item.equals("博客园")) {
            platformList.remove(i);
            i = i - 1;
        }
    }
    System.out.println(platformList);
}

这种实现方式比较好理解,就是通过数组的下标来删除,不过有个注意事项就是删除元素后,要修正下下标的值:
在这里插入图片描述为什么要修正下标的值呢?
因为刚开始元素的下标是这样的:
在这里插入图片描述
第1次循环将元素"博客园"删除后,元素的下标变成了下面这样:
在这里插入图片描述
这样导致所有的索引都少了1,所以需要进行修正下标值

3、使用for循环倒序遍历

public static void main3(String[] args) {
    List<String> platformList = new ArrayList<>();
    platformList.add("博客园");
    platformList.add("CSDN");
    platformList.add("掘金");

    for (int i = platformList.size() - 1; i >= 0; i--) {
        String item = platformList.get(i);

        if (item.equals("掘金")) {
            platformList.remove(i);
        }
    }

    System.out.println(platformList);
}

这种实现方式和使用for循环正序遍历类似,不过不用再修正下标,因为刚开始元素的下标是这样的:
在这里插入图片描述
第1次循环将元素"掘金"删除后,元素的下标变成了下面这样:
在这里插入图片描述
不影响原来数据的索引值,所以不需要修正。

回头注意一下,后两种使用for循环,并通过索引操作,并没有被转译成迭代器遍历,来看看class文件:

正序移除:
在这里插入图片描述
倒序移除:
在这里插入图片描述

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在Java的forEach循环中,你不能直接使用`remove`方法来删除集合中的元素。这是因为`forEach`循环是基于迭代器的,而在迭代过程中直接调用`remove`方法会导致`ConcurrentModificationException`并发修改异常。 如果你需要在遍历过程中删除集合中的元素,可以使用迭代器的`remove`方法。以下是一个示例代码,演示如何在遍历List时使用迭代器的`remove`方法删除元素: ```java import java.util.ArrayList; import java.util.Iterator; import java.util.List; public class Main { public static void main(String[] args) { // 创建一个List List<String> list = new ArrayList<>(); list.add("Apple"); list.add("Banana"); list.add("Orange"); // 获取List的迭代器 Iterator<String> iterator = list.iterator(); // 遍历List并删除元素 while (iterator.hasNext()) { String element = iterator.next(); if (element.equals("Banana")) { iterator.remove(); // 使用迭代器的remove方法删除元素 } } // 打印删除后的List System.out.println(list); // 输出: [Apple, Orange] } } ``` 在上述代码中,我们通过调用`iterator()`方法获取List的迭代器对象。然后,在遍历过程中使用`next()`方法获取下一个元素,并使用`remove()`方法删除指定的元素。注意,这里我们使用了迭代器的`remove()`方法,而不是List的`remove()`方法。 这样就可以在遍历List的过程中删除元素了。希望对你有所帮助!如果你还有其他问题,请随时提问。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值