【JAVA基础】一文了解forEach循环

news2025/1/11 18:37:29

前言

相信大家肯定都看过阿里巴巴开发手册,而在阿里巴巴开发手册中明确的指出,不要再foreach循环里面进行元素的add和remove,如果你非要进行remove元素,那么请使用Iterator方式,如果存在并发,那么你一定要选择加锁。

foreach

    public static void main(String[] args) {
        List<String> list = new ArrayList<String>();
        list.add("11");
        list.add("22");
        list.add("33");
        list.add("44");

        for (String s : list) {
            if ("22".equalsIgnoreCase(s)) {
                list.remove(s);
            }
        }
        System.out.println(JSONObject.toJSONString(list));
    }

输出结果:

Exception in thread "main" java.util.ConcurrentModificationException
	at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:901)
	at java.util.ArrayList$Itr.next(ArrayList.java:851)
	at org.example.list.Test01.main(Test01.java:22)

Process finished with exit code 1

分析异常:

final void checkForComodification() {
    if (modCount != expectedModCount)
        throw new ConcurrentModificationException();
}

比较两个值 modCountexpectedModCount,那么这两个变量是什么呢?

其中modCount表示集合的修改次数,这其中包括了调用集合本身的add方法等修改方法时进行的修改和调用集合迭代器的修改方法进行的修改。而expectedModCount则是表示迭代器对集合进行修改的次数。

先来看看反编译之后的代码,如下:

    public static void main(String[] args) {
        List<String> list = new ArrayList();
        list.add("11");
        list.add("22");
        list.add("33");
        list.add("44");
        Iterator var2 = list.iterator();

        while(var2.hasNext()) {
            String s = (String)var2.next();
            if ("22".equalsIgnoreCase(s)) {
                list.remove(s);
            }
        }

        System.out.println(JSONObject.toJSONString(list));
    }

看里面使用的也是迭代器,也就是说,其实 foreach 每次循环都调用了一次iteratornext()方法,
foreach方式中调用的remove方法,是ArrayList内部的remove方法,会更新modCount属性

我们可以看看ArrayList类中的remove方法

    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;
    }

看到此方法中,有一个modCount++的操作,也就是说,modCount会一直更新变化。

我们第一次迭代的时候 11 != 22 ,直接迭代第二次,这时候就相等了,执行remove()方法,这时候就是modCount++,再次调用next()的时候,modCount = expectedModCount 这个就不成立了,所以异常信息出现了,其实也可以理解为在 hasNext() 里面,cursor != size 而这时候就会出现错误了。

也就是说 remove方法它只修改了modCount,并没有对expectedModCount做任何操作。

迭代器

为什么阿里巴巴的规范手册会这样子定义?

img

它为什么推荐我们使用 Iterator呢?

直接使用迭代器会修改expectedModCount,而我们使用foreach的时候,remove方法它只修改了modCount,并没有对expectedModCount做任何操作,而Iterator就不会这个样子。

   public static void main(String[] args) {
        List<String> list = new ArrayList<String>();
        list.add("11");
        list.add("22");
        list.add("33");
        list.add("44");

        Iterator<String> iterator = list.iterator();
        while (iterator.hasNext()){
            String item = iterator.next();
            if("22".equals(item)){
                iterator.remove();
            }
        }
        System.out.println(JSONObject.toJSONString(list));
    }

输出结果:

["11","33","44"]

Process finished with exit code 0

可以看出结果是正确的,下面我们来分析一下:

先来看看反编译之后的代码:

    public static void main(String[] args) {
        List<String> list = new ArrayList();
        list.add("11");
        list.add("22");
        list.add("33");
        list.add("44");
        Iterator iterator = list.iterator();

        while(iterator.hasNext()) {
            String item = (String)iterator.next();
            if ("22".equals(item)) {
                iterator.remove();
            }
        }

        System.out.println(JSONObject.toJSONString(list));
    }

主要观察remove()方法的实现,那么需要先看 ArrayList.class:

    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 boolean hasNext() {
            return cursor != size;
        }

        public void remove() {
            if (lastRet < 0)
                throw new IllegalStateException();
            checkForComodification();     //第一步

            try {
                ArrayList.this.remove(lastRet);   //第二步:调用list的remove方法
                cursor = lastRet;
                lastRet = -1;
                expectedModCount = modCount; 		//第三步:modCount是remove方法去维护更新,
                                                    //由于第一步中校验 modCount 和 expectedModCount 是否相当等
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
        }

        final void checkForComodification() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }
    }
  1. 调用 checkForComodification()方法,作用:判断modCountexpectedModCount 是否相当;

  2. foreach 方式中调用的remove方法,是ArrayList内部的remove方法,会更新modCount属性;

  3. 将更新后的modCount重新赋值给expectedModCount变量。

Java8的新特性

    public static void main(String[] args) {
        List<String> list = new ArrayList<String>();
        list.add("11");
        list.add("22");
        list.add("33");
        list.add("44");

        list.removeIf("22"::equals);
        System.out.println(JSONObject.toJSONString(list));
    }

总结

for-each循环不仅适用于遍历集合和数组,而且能让你遍历任何实现Iterator接口的对象;最最关键的是它还没有性能损失。而对数组或集合进行修改(添加删除操作),就要用迭代器循环。所以循环遍历所有数据的时候,能用它的时候还是选择它吧。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/694244.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

el-table控制列的显示与隐藏

1、序言 源码在下方&#xff0c;复制粘贴就可运行 当一个表格太多列的时候&#xff0c;想要显示/隐藏一些列&#xff0c;目标效果如下&#xff1a; 默认情况下&#xff0c;展示所有列 隐藏某一列 2、原理 &#xff08;1&#xff09;data数据有&#xff1a;tableData为表格数据&…

c++类和对象收尾

文章目录 隐式类型转换匿名对象拷贝构造----->构造 隐式类型转换 //隐式类型转换 class A { public:/*explicit A(int a ):_a(a){cout << "A(int a )" << endl;}*/A(int a):_a(a){cout << "A(int a )" << endl;}A(const A&…

4. QT环境下使用OPenCV(视频或摄像头读取显示在QLabel控件上)

1. 说明 在用opencv处理图像时,图像的来源大部分情况下是从视频中读取过来的,视频可以是本地保存的视频,也可以是本地摄像头或者网络摄像头实时拍摄的视频。 效果展示: opencv读取视频 2. 具体操作 关于视频的读取,实际上也是从视频中将每一帧图像加载后,显示到QLabel…

【数据可视化方案分享】电商数据分析

本文所分享的电商数据分析报表均来自奥威BI软件的电商数据分析方案&#xff01;该方案是一套包含数据采集、数据建模、数据分析报表的系统化、标准化数据分析方案&#xff0c;下载套用&#xff0c;立见效果&#xff01; 注意&#xff0c;奥威BI软件的电商数据分析方案分两类&a…

Elasticsearch Global Ordinals

用于减少字符串字段 fielddata 内存使用的技术之一称为序数&#xff08;ordinals&#xff09;。想象一下&#xff0c;我们有十亿个文档&#xff0c;每个文档都有一个状态字段。 只有三种状态&#xff1a;status_pending、status_published、status_deleted。 如果我们要在内存中…

Ubuntu离线安装Telnet服务

通过ssh上传telnet包&#xff0c;下载地址&#xff1a;telnet-0.17-41.2build1-amd64资源-CSDN文库 解压telnet包&#xff1a; tar -xzvf telnet_0.17-41.2build1_amd64.tar.gz 安装telnet服务&#xff1a; dpkg -i telnet_0.17-41.2build1_amd64.deb 安装完毕&#xff0c;测…

2023第三届中国RPA+AI开发者大赛圆满收官获奖名单公示

6月26日&#xff0c;历时三个月的2023「第三届中国RPAAI开发者大赛」在苏州国际博览中心圆满收官。本次大赛以“探索创新智能融合”为主题&#xff0c;旨在寻找并推动中国RPA和AI领域的创新人才和前沿技术。云集来自各大行业与领域的企业、高校等326支优秀团队&#xff0c;共计…

chatgpt赋能python:Python读取xlsx中的超链接

Python读取xlsx中的超链接 xlsx是一种常用的电子表格文件格式&#xff0c;在日常的工作生活中经常使用。xlsx文件中可以包含超链接&#xff0c;作为文件中数据的补充和扩展。而Python作为一门强大的编程语言&#xff0c;可以帮助我们轻松读取xlsx文件中的超链接&#xff0c;进…

HarmonyOS/OpenHarmony应用开发-Stage模型UIAbility组件使用(一)

一、UIAbility组件概述1.概述 UIAbility组件是一种包含UI界面的应用组件&#xff0c;主要用于和用户交互。 UIAbility组件是系统调度的基本单元&#xff0c;为应用提供绘制界面的窗口&#xff1b;一个UIAbility组件中可以通过多个页面来实现一个功能模块。每一个UIAbility组件实…

督查督办管理系统新旧时代

旧时代&#xff1a; 传统的督办主要是通过督办人员现场调查催办、电话催办提交工作进展情况等方法&#xff0c;加上市面上的成品型督办系统功能固化&#xff0c;无法根据企业个性化业务需求灵活调整&#xff0c;所以在工作督办上存在诸多不足。 数据管理滞后&#xff0c;效率不…

【ASP技术】web杂谈(1)之什么是ASP?

涉及知识点 什么是 ASP&#xff0c;Request和Response的介绍&#xff0c;Application和session的详细讲解&#xff0c;ASP的特点&#xff0c;ASP的编程环境&#xff0c;ASP内嵌对象&#xff0c;Asp的应用范例。深入了解ASP技术。 原创于&#xff1a;CSDN博主-《拄杖盲学轻声码…

npm install执行报错:ENOENT: no such file or directory, open ‘XXXXX\package.json‘

执行 npm install 报错 解决办法&#xff1a; 先执行 npm init -f 再安装 npm install

使用Python+Flask+HTML写一个测试小工具

背景&#xff1a; 由于公司安全规范限制&#xff0c;我司的测试、预发布、灰度、生产环境&#xff0c;接口间的数据通信通常是加密处理的&#xff0c;给我们日常定位缺陷带来了一定的阻碍&#xff0c;因此我决定使用Python写一个工具&#xff0c;将加密数据转换为正常数据&…

vscode面板菜单字体太大或者太小解决方式【自留记录】

vscode面板菜单字体太大或者太小解决方式【自留记录】 菜单图标 - 设置 - 搜索栏输入 zoom - 输入 0 回车

jasny-bootstrap组件-文件上传

今天和大家分享jasny-bootstrap如何实现文件上传&#xff0c;以及同form表单同时提交处理。 目前各大网站关于jasny-bootstrap上传文件的案例demo少之又少&#xff0c;就连若依也只是一笔带过&#xff0c;只是做了一个纯效果展示。 经过博主的一番查阅文档&#xff0c;查找案例…

【Android安全】Flutter app逆向

使用《Flutter逆向助手》逆向Flutter app 参考&#xff1a;https://www.bilibili.com/video/BV1SM41147g5 flutter app特征&#xff1a; lib/armeabi-v7a/下面有libapp.so和libflutter.so 逆向分析工具&#xff1a; 看雪&#xff1a;Flutter逆向助手 可以解析libapp.so文件…

OpenCV——《直方图操作》和《模版匹配》

1.直方图均衡化 img cv2.imread(clahe.jpg,0) plt.hist(img.ravel(),256) plt.show() #旨在使得图像整体效果均匀&#xff0c;黑与白之间的各个像素级之间的点更均匀一点。 equ cv2.equalizeHist(img) plt.hist(equ.ravel(),256) plt.show() #进行对比&#xff0c;均值化之后…

mapbox图层层级问题

如果在项目之初我们没有设计好图层的问题&#xff0c;那么大概率我们会与到预期图层在别的图层下面的问题&#xff0c;这是需要使用addlayer、movelayer方法来调整图层的位置了。 一般而言先添加的图层在显示的时候在后添加图层的下面&#xff0c;例如下面的代码&#xff1a; …

ChatGPT伪原创文章的应用与发展

ChatGPT是一种基于人工智能技术的自然语言处理模型&#xff0c;它能够生成逼真的、具有上下文连贯性的文本。近年来&#xff0c;ChatGPT在各个领域的应用越来越广泛&#xff0c;其发展潜力也逐渐被人们所认识。本文将从多个方面对ChatGPT的应用与发展进行详细阐述。 ChatGPT在…

人脸考勤签到进阶篇

目录 签到业务流程说明 一、需求介绍 二、如何获取地理信息&#xff1f; 三、如何判定某地区新冠疫情的风险等级&#xff1f; 开通腾讯位置服务 二、腾讯位置服务SDK 把定位坐标转换成真实地址 一、获取定位坐标 uni.authorize(OBJECT) 二、编辑签到页面 在Docker中…