首页 > 其他分享 >for-each循环陷阱

for-each循环陷阱

时间:2024-07-13 23:20:47浏览次数:6  
标签:ArrayList 元素 list remove modCount 循环 陷阱 each expectedModCount

for-each删除元素报错

public static void main(String[] args) {
    List<String> list = new ArrayList<>();
    list.add("haha");
    list.add("xixi");
    list.add("hehe");

    for (String s : list) {
        if ("haha".equals(s))
            list.remove(s);
    }

    System.out.println(list);
}
Exception in thread "main" java.util.ConcurrentModificationException
	at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:911)
	at java.util.ArrayList$Itr.next(ArrayList.java:861)
	at test.foreachTest.Test.main(Test.java:13)
  • remove 的时候触发执行了 checkForComodification 方法,该方法对 modCount 和 expectedModCount 进行了比较,发现两者不等,就抛出了 ConcurrentModificationException 异常。

  • ArrayList重写了Iterable的iterator方法

public Iterator<E> iterator() {
    return new Itr();
}
// 计数器,用于记录 ArrayList 对象被修改的次数。ArrayList 的修改操作包括添加、删除、设置元素值等。每次对 ArrayList 进行修改操作时,modCount 的值会自增 1。
protected transient int modCount = 0;

private class Itr implements Iterator<E> {
    int cursor; 
    int lastRet = -1;
    // new Itr() 的时候 expectedModCount 被赋值为 modCount
    int expectedModCount = modCount;

    Itr() {}

    // 判断是否还有下个元素
    public boolean hasNext() {
        return cursor != size;
    }

    @SuppressWarnings("unchecked")
    // 获取下个元素
    public E next() {
        // 检查 ArrayList 是否被修改过
        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();
    }
}
  • fail-fast 是一种通用的系统设计思想,一旦检测到可能会发生错误,就立马抛出异常,程序将不再往下执行。
  • 在迭代 ArrayList 时,如果迭代过程中发现 modCount 的值与迭代器的 expectedModCount 不一致,则说明 ArrayList 已被修改过,此时会抛出 ConcurrentModificationException 异常。这种机制可以保证迭代器在遍历 ArrayList 时,不会遗漏或重复元素,同时也可以在多线程环境下检测到并发修改问题。

执行逻辑

  • list执行3次add,每次add都会调用 ensureCapacityInternal 方法,ensureCapacityInternal 方法调用 ensureExplicitCapacity 方法,ensureExplicitCapacity 方法中会执行 modCount++。三次add后modCount为3

  • 第一次遍历时,执行remove,remove 方法调用 fastRemove 方法,fastRemove 方法中会执行 modCount++,modCound变成4

  • 第二次遍历时,会执行 Itr 的 next 方法,next 方法就会调用 checkForComodification 方法。此时 expectedModCount 为 3,modCount 为 4,抛出 ConcurrentModificationException 异常。

正确删除元素

remove后break

// 没法删除多个重复元素
for (String s : list) {
    if ("haha".equals(s)) {
        list.remove(s);
        break;
    }
}

for循环

for (int i = 0; i < list.size(); i++) {
    String s = list.get(i);
    if ("haha".equals(s)) {
        // 删除后,size减一,list中后一个元素会移到被删除的下标i处
        // 但下次循环不会再遍历下标i处的元素了
        list.remove(s);
    }
}

Iterator自带的remove()

Iterator<String> itr = list.iterator();
while (itr.hasNext()) {
    String s = itr.next();
    if ("haha".equals(s))
        itr.remove();
}
  • ArrayList中的内部类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 E next() {
        ...
        // 记录这次调用next返回的元素
        return (E) elementData[lastRet = i];
    }
    
    public void remove() {
        // 如果没有上一个返回元素的索引,则抛出异常
        if (lastRet < 0)
            throw new IllegalStateException();
        // 检查 ArrayList 是否被修改过
        checkForComodification();

        try {
            // 删除上一个返回元素,也就是上次调用next返回的元素
            ArrayList.this.remove(lastRet);
            // 更新下一个元素的索引
            cursor = lastRet;
            // 清空上一个返回元素的索引
            lastRet = -1;
            // 更新 ArrayList 的修改次数,保证了 expectedModCount 与 modCount 的同步
            expectedModCount = modCount;
        } catch (IndexOutOfBoundsException ex) {
            throw new ConcurrentModificationException();
        }
    }

    ...
}

  • 采用 Stream 流的filter() 方法来过滤集合中的元素,然后再通过 collect() 方法将过滤后的元素收集到一个新的集合中。
List<String> list = new ArrayList<>(Arrays.asList("haha", "xixi", "hehe"));
list = list.stream().filter(s -> !s.equals("haha")).collect(Collectors.toList());

总结

  • 之所以不能在foreach里执行删除操作,是因为foreach 循环是基于迭代器实现的,而迭代器在遍历集合时会维护一个 expectedModCount 属性来记录集合被修改的次数。如果在 foreach 循环中执行删除操作会导致 expectedModCount 属性值与实际的 modCount 属性值不一致,从而导致迭代器的 hasNext() 和 next() 方法抛出 ConcurrentModificationException 异常。
  • 为了避免这种情况,应该使用迭代器的 remove() 方法来删除元素,该方法会在删除元素后更新迭代器状态,确保循环的正确性。如果需要在循环中删除元素,应该使用迭代器的 remove() 方法,而不是集合自身的 remove() 方法。

标签:ArrayList,元素,list,remove,modCount,循环,陷阱,each,expectedModCount
From: https://www.cnblogs.com/sprinining/p/18300959

相关文章

  • 【CPO-TCN-BiGRU-Attention回归预测】基于冠豪猪算法CPO优化时间卷积双向门控循环单元
    %数据准备%假设有一个输入变量X和一个目标变量Y%假设数据已经存储在X和Y中,每个变量为列向量%参数设置inputWindowSize=10;%输入窗口大小outputWindowSize=1;%输出窗口大小numFeatures=1;%输入变量的数量numFilters=32;%TCN中的滤波器数......
  • 流程循环控制语句
    目录for循环带列表循环不带列表循环类C风格循环while循环语法无限循环使用示例until循环基本语法示例select循环语法格式嵌套循环break和continuebreak的使用continue的使用在shell中循环有以下几种:for循环        while循环until循环selec......
  • 7-循环队列的基本操作
    顺序队列的操作#include<stdio.h>#include<stdlib.h>#include<stdbool.h>typedefintElemType;#defineMaxSize50/*顺序队列的类型定义*/typedefstruct{/*用一维数组存放队列元素*/ElemTypedata[MaxSize];/*队头指针*/intfront;/*......
  • 《穿透财报:读懂财报中的逻辑与陷阱》
    《逆向投资 邓普顿的长赢投资法》的后面五章《面对泡沫:拿出卖空的勇气》《在危机中寻找时机》《关注长期前景,发现历史规律》《债券:长期投资的首选》《投资中国:巨龙从沉睡中觉醒》读下来完全没有什么感触,也没什么读后感好写。不知道是劳伦不如林奇,还是我心里已经打上了这样的标......
  • C语言-分支与循环(1)
    目录1、if语句1.1if1.2else1.3分支中包含多条语句1.4嵌套if1.5悬空else问题(多个if和一个else对应关系)2、switch语句2.1switch语句中的break2.2switch语句中的default2.3switch语句中的case和default的顺序问题3、关系操作符4、条件操作符4.1什么是条件......
  • mybatis 中 foreach collection的三种用法
    转载:http://blog.sina.com.cn/s/blog_b0d90e8c0102v1q1.htmlforeach的主要用在构建in条件中,它可以在SQL语句中进行迭代一个集合。foreach元素的属性主要有item,index,collection,open,separator,close。    item表示集合中每一个元素进行迭代时的别名,    index指定一个名字,......
  • 易优cms网站for功能:数据/记录循环输出标签(注:类似与volist、foreach标签)-Eyoucms
    【基础用法】名称:for功能:数据/记录循环输出标签(注:类似与volist、foreach标签)语法:{eyou:forstart='开始值'end='结束值'}{$i}{/eyou:for}php解析后的代码是:for($i=1;$i<100;$i+=1){echo$i;}参数:start=''开始值end=''结束值comparison=''比较操作符,默认是小于......
  • 易优cms网站volist功能:数据/记录循环输出标签-Eyoucms
    【基础用法】名称:volist功能:数据/记录循环输出标签语法:{eyou:channeltype='top'}      {eyou:volistname='$field.children'id='field1'}<ahref='{$field1.typeurl}'>{$field1.typename}</a>{/eyou:volist}{/eyou:channel}文件:无参数:......
  • 易优cms网站foreach功能:数据/记录循环输出标签(注:类似与volist标签,只是更加简单,没有太
    【基础用法】名称:foreach功能:数据/记录循环输出标签(注:类似与volist标签,只是更加简单,没有太多额外的属性。)语法:{eyou:channeltype='top'}{eyou:foreachname='$field.children'item='field1'}<ahref='{$field1.typeurl}'>{$field1.typename}</a>{/eyou:......
  • 【AI前沿】深度学习基础:循环神经网络(RNN)
    文章目录......