Java中的forEach循环详细解读

 更新时间:2023年12月19日 09:11:10   作者:初念初恋  
这篇文章主要介绍了Java中的forEach循环详细解读,不要再foreach循环里面进行元素的add和remove,如果你非要进行remove元素,那么请使用Iterator方式,如果存在并发,那么你一定要选择加锁,需要的朋友可以参考下

前言

相信大家肯定都看过阿里巴巴开发手册,而在阿里巴巴开发手册中明确的指出,不要再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();
}

比较两个值 modCount 和expectedModCount,那么这两个变量是什么呢?

其中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 每次循环都调用了一次iterator的next()方法, 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()方法,作用:判断modCount 和 expectedModCount 是否相当;
  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接口的对象;最最关键的是它还没有性能损失。

而对数组或集合进行修改(添加删除操作),就要用迭代器循环。所以循环遍历所有数据的时候,能用它的时候还是选择它吧。

到此这篇关于Java中的forEach循环详细解读的文章就介绍到这了,更多相关forEach循环内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 使用mockito编写测试用例教程

    使用mockito编写测试用例教程

    这篇文章主要为大家介绍了使用mockito编写测试用例教程详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-08-08
  • SpringCloud feign服务熔断下的异常处理操作

    SpringCloud feign服务熔断下的异常处理操作

    这篇文章主要介绍了SpringCloud feign服务熔断下的异常处理操作,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-06-06
  • springboot:接收date类型的参数方式

    springboot:接收date类型的参数方式

    这篇文章主要介绍了springboot:接收date类型的参数方式,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-10-10
  • Java Http多次请求复用同一连接示例详解

    Java Http多次请求复用同一连接示例详解

    这篇文章主要为大家介绍了Java Http多次请求复用同一连接示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-10-10
  • java实现图书管理系统

    java实现图书管理系统

    这篇文章主要为大家详细介绍了java实现图书管理系统,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-03-03
  • springboot 如何取消starter的自动注入

    springboot 如何取消starter的自动注入

    这篇文章主要介绍了springboot 如何取消starter的自动注入操作,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-09-09
  • 在springboot中如何集成clickhouse进行读写操作

    在springboot中如何集成clickhouse进行读写操作

    本文介绍了在Spring Boot中集成ClickHouse的步骤,包括引入依赖、配置数据源、编写实体类和Mapper类进行CRUD操作,特别提到批量插入时需要在SQL语句中添加`FORMAT`以避免错误,在实际应用中,与MySQL的操作类似,只需将ClickHouse当作MySQL使用
    2024-11-11
  • Java多线程atomic包介绍及使用方法

    Java多线程atomic包介绍及使用方法

    这篇文章主要介绍了Java多线程atomic包介绍及使用方法,涉及原子更新基本类型介绍及代码示例,具有一定参考价值,需要的朋友可以了解下。
    2017-11-11
  • springboot validator枚举值校验功能实现

    springboot validator枚举值校验功能实现

    这篇文章主要介绍了springboot validator枚举值校验功能实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-01-01
  • java实现MD5加密方法汇总

    java实现MD5加密方法汇总

    本文给大家汇总介绍了2种java实现MD5加密的方法,非常的实用,这里分享给大家,学习下其中的思路,对大家学习java非常有帮助。
    2015-10-10

最新评论