为什么在foreach循环中JAVA集合不能添加或删除元素


Posted in Java/Android onJune 11, 2021

1. 编码强制规约

在《阿里巴巴Java开发手册》中,针对集合操作,有一项规定,如下:

【强制】不要在 foreach 循环里进行元素的 remove/add 操作。remove 元素请使用 Iterator方式,如果并发操作,需要对 Iterator 对象加锁。

public class SimpleTest {
    public static void main(String[] args) {
        List<String> list = Lists.newArrayList();
        list.add("1");
        list.add("2");
        list.add("3");
        list.add("4");
 
        //正例
        Iterator<String> iterator = list.iterator();
        while (iterator.hasNext()) {
            String item = iterator.next();
            if ("1".equalsIgnoreCase(item)) {
                iterator.remove();
            }
        }
 
        //反例
        for (String item : list) {
            if ("2".equals(item)) {
                list.remove(item);
            }
        }
    }
}

2. 原因分析

在循环或迭代时,会首先创建一个迭代实例,这个迭代实例的expectedModCount 赋值为集合的modCount.   

每当迭代器使⽤ hashNext() / next() 遍历下⼀个元素之前,都会检测 modCount 变量与expectedModCount 值是否相等,相等的话就返回遍历;否则就抛出异常【ConcurrentModificationException】,终⽌遍历

如果在循环中添加或删除元素,是直接调用集合的add,remove方法【导致了modCount增加或减少】,但这些方法不会修改迭代实例中的expectedModCount,导致在迭代实例中expectedModCount 与 modCount的值不相等,抛出ConcurrentModificationException异常

但迭代器中的remove,add方法,会在调用集合的remove,add方法后,将expectedModCount 重新赋值为modCount,所以在迭代器中增加、删除元素是可以正常运行的。

可以参考ArrayList中的内部私有类Itr、ListItr的源码

public Iterator<E> iterator() {
        return new 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;
 
        Itr() {}
 
        //删除了一些代码
 
        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();
            }
        }
 
        final void checkForComodification() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }
 
  }
   public E remove(int index) {
        rangeCheck(index);
 
        modCount++;
        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;
    }

3. 相关知识介绍

3.1. 什么是快速失败(fail-fast)?

快速失败(fail-fast) 是 Java 集合的⼀种错误检测机制。在使⽤迭代器对集合进⾏遍历的时候,在多线程下操作⾮安全失败(fail-safe)的集合类可能就会触发 fail-fast 机制,导致抛出ConcurrentModificationException 异常。 

另外,在单线程下,如果在遍历过程中对集合对象的内容进⾏了修改的话也会触发 fail-fast 机制。

举个例⼦:多线程下,如果线程 1 正在对集合进⾏遍历,此时线程 2 对集合进⾏修改(增加、删除、修改),或者线程 1 在遍历过程中对集合进⾏修改,都会导致线程 1 抛出ConcurrentModificationException 异常。

3.2. 什么是安全失败(fail-safe)呢?

采⽤安全失败机制的集合容器,在遍历时不是直接在集合内容上访问的,⽽是先复制原有集合内容,在拷⻉的集合上进⾏遍历。所以,在遍历过程中对原集合所作的修改并不能被迭代器检测到,故不会抛ConcurrentModificationException 异常。

到此这篇关于为什么在foreach循环中JAVA集合不能添加或删除元素的文章就介绍到这了,更多相关JAVA集合添加或删除元素内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木!

Java/Android 相关文章推荐
JVM入门之类加载与字节码技术(类加载与类的加载器)
Jun 15 Java/Android
浅谈什么是SpringBoot异常处理自动配置的原理
Jun 21 Java/Android
java中重写父类方法加不加@Override详解
Jun 21 Java/Android
java泛型通配符详解
Jul 25 Java/Android
Java移除无效括号的方法实现
Aug 07 Java/Android
SpringBoot实现quartz定时任务可视化管理功能
Aug 30 Java/Android
SpringDataJPA在Entity中常用的注解介绍
Dec 06 Java/Android
Flutter集成高德地图并添加自定义Maker的实践
Apr 07 Java/Android
Java时间工具类Date的常用处理方法
May 25 Java/Android
Spring中的@Transactional的工作原理
Jun 05 Java/Android
springboot为异步任务规划自定义线程池的实现
Jun 14 Java/Android
Java实现注册登录跳转
Jun 16 Java/Android
springboot @ConfigurationProperties和@PropertySource的区别
教你用Java Swing实现自助取款机系统
总结一些Java常用的加密算法
Jun 11 #Java/Android
手把手教你用SpringBoot将文件打包成zip存放或导出
源码解读Spring-Integration执行过程
浅谈Java实现分布式事务的三种方案
分享一些Java的常用工具
You might like
php使用qr生成二维码的示例分享
2014/01/20 PHP
PHP的mysqli_query参数MYSQLI_STORE_RESULT和MYSQLI_USE_RESULT的区别
2014/09/29 PHP
详解PHP中cookie和session的区别及cookie和session用法小结
2016/06/12 PHP
PHP获取链表中倒数第K个节点的方法
2018/01/18 PHP
[原创]网络复制内容时常用的正则+editplus
2006/11/30 Javascript
xml文档转换工具,附图表例子(hta)
2010/11/17 Javascript
js 创建书签小工具之理论
2011/02/25 Javascript
JS函数重载的解决方案
2014/05/13 Javascript
javascript中call apply 的应用场景
2015/04/16 Javascript
jquery实现像栅栏一样左右滑出式二级菜单效果代码
2015/08/24 Javascript
jQuery+json实现的简易Ajax调用实例
2015/12/14 Javascript
angularjs实现文字上下无缝滚动特效代码
2016/09/04 Javascript
Java  Spring 事务回滚详解
2016/10/17 Javascript
JavaScript实现焦点进入文本框内关闭输入法的核心代码
2017/09/20 Javascript
利用pm2部署多个node.js项目的配置教程
2017/10/22 Javascript
vue 登录滑动验证实现代码
2018/08/24 Javascript
JavaScript变量提升和严格模式实例分析
2019/01/27 Javascript
原生JS forEach()和map()遍历的区别、兼容写法及jQuery $.each、$.map遍历操作
2019/02/27 jQuery
vue单页面在微信下只能分享落地页的解决方案
2019/04/15 Javascript
ES6基础之 Promise 对象用法实例详解
2019/08/22 Javascript
详解基于Vue/React项目的移动端适配方案
2019/08/23 Javascript
layer.alert回调函数执行关闭弹窗的实例
2019/09/11 Javascript
详解JS预解析原理
2020/06/16 Javascript
[01:12](回顾)DOTA2国际邀请赛,全世界DOTAer的盛宴
2014/07/01 DOTA
[48:46]完美世界DOTA2联赛PWL S2 SZ vs FTD.C 第二场 11.19
2020/11/19 DOTA
深入理解Python对Json的解析
2017/02/14 Python
使用python获取电脑的磁盘信息方法
2018/11/01 Python
nginx搭建基于python的web环境的实现步骤
2020/01/03 Python
python爬虫要用到的库总结
2020/07/28 Python
MADE荷兰:提供原创设计师家具
2018/04/03 全球购物
电脑教师的自我评价
2013/12/18 职场文书
小学语文课后反思精选
2014/04/25 职场文书
新郎婚礼致辞
2015/07/27 职场文书
如何使用Python实现一个简易的ORM模型
2021/05/12 Python
Python实战之实现简易的学生选课系统
2021/05/25 Python
python 进阶学习之python装饰器小结
2021/09/04 Python