为什么在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 相关文章推荐
如何解决springcloud feign 首次调用100%失败的问题
Jun 23 Java/Android
Spring Boot 启动、停止、重启、状态脚本
Jun 26 Java/Android
Java基础之详解HashSet的使用方法
Jun 30 Java/Android
spring boot项目application.properties文件存放及使用介绍
Jun 30 Java/Android
SpringBoot 拦截器妙用你真的了解吗
Jul 01 Java/Android
mybatis中注解与xml配置的对应关系和对比分析
Aug 04 Java/Android
Java后端 Dubbo retries 超时重试机制的解决方案
Apr 14 Java/Android
SpringBoot 集成短信和邮件 以阿里云短信服务为例
Apr 22 Java/Android
带你了解Java中的ForkJoin
Apr 28 Java/Android
Springboot中如何自动转JSON输出
Jun 16 Java/Android
Spring Cloud OpenFeign模版化客户端
Jun 25 Java/Android
springboot @ConfigurationProperties和@PropertySource的区别
教你用Java Swing实现自助取款机系统
总结一些Java常用的加密算法
Jun 11 #Java/Android
手把手教你用SpringBoot将文件打包成zip存放或导出
源码解读Spring-Integration执行过程
浅谈Java实现分布式事务的三种方案
分享一些Java的常用工具
You might like
常用表单验证类,有了这个,一般的验证就都齐了。
2006/12/06 PHP
php实现在多维数组中查找特定value的方法
2015/07/29 PHP
PHP下的浮点运算不准的解决方法
2016/10/27 PHP
centos7上编译安装php7以php-fpm方式连接apache
2018/11/08 PHP
php和C#的yield迭代器实现方法对比分析
2019/07/17 PHP
JS获取页面input控件中所有text控件并追加样式属性
2013/02/25 Javascript
Javascript表格翻页效果实现思路及代码
2013/08/23 Javascript
利用js实现前台动态添加文本框,后台获取文本框内容(示例代码)
2013/11/25 Javascript
Js操作树节点自动折叠展开的几种方法
2014/05/05 Javascript
浅谈javascript事件取消和阻止冒泡
2015/05/26 Javascript
解析Node.js基于模块和包的代码部署方式
2016/02/16 Javascript
jqGrid用法汇总(全经典)
2016/06/28 Javascript
jquery获取链接地址和跳转详解(推荐)
2017/08/15 jQuery
利用node实现一个批量重命名文件的函数
2017/12/21 Javascript
基于Axios 常用的请求方法别名(详解)
2018/03/13 Javascript
ES6 Class中实现私有属性的一些方法总结
2019/07/08 Javascript
js cavans实现静态滚动弹幕
2020/05/21 Javascript
[01:07]2015国际邀请赛 中国区预选赛精彩回顾
2015/06/15 DOTA
python出现&quot;IndentationError: unexpected indent&quot;错误解决办法
2017/10/15 Python
简单了解Python3里的一些新特性
2019/07/13 Python
ipad上运行python的方法步骤
2019/10/12 Python
python读取Kafka实例
2019/12/23 Python
CSS3属性box-shadow使用指南
2014/12/09 HTML / CSS
耐克美国官网:Nike.com
2016/08/01 全球购物
Ray-Ban雷朋西班牙官网:全球领先的太阳眼镜品牌
2018/11/28 全球购物
世界上第一个创建了罩杯系统的美国内衣品牌:Maidenform
2019/03/23 全球购物
Linux面试经常问的文件系统操作命令
2015/11/05 面试题
公司捐款倡议书
2014/05/14 职场文书
技校毕业生自荐书
2014/05/23 职场文书
中职生自荐信范文
2014/06/15 职场文书
欢迎标语大全
2014/06/21 职场文书
群众路线教育实践活动自我剖析思想汇报
2014/10/04 职场文书
同学毕业留言寄语
2015/02/27 职场文书
售房协议书范本
2015/08/11 职场文书
tensorflow中的梯度求解及梯度裁剪操作
2021/05/26 Python
Android开发手册Chip监听及ChipGroup监听
2022/06/10 Java/Android