程序员子龙(Java面试 + Java学习) 程序员子龙(Java面试 + Java学习)
首页
学习指南
工具
开源项目
技术书籍

程序员子龙

Java 开发从业者
首页
学习指南
工具
开源项目
技术书籍
  • 基础

    • java 学习路线图
    • HashMap 详解
    • Java 8 日期和时间 - 如何获取当前时间和时间戳?
    • Java 模板变量替换(字符串、占位符替换)
    • JDK 代理
    • Java SPI 详解
    • java stream 看这一篇文章就够了
    • Java 泛型详解
    • Java 动态修改注解值
    • 如何正确遍历删除List中的元素
      • 什么是Java注解
      • 异步编程神器:CompletableFuture详解
      • FIFO 、LRU、LFU算法
      • 十进制转十六进制
      • java中double类型数据加减乘除操作精度丢失问题及解决方法
      • JAVA利用反射清除实体类对应字段
      • JSON转换问题最全详解(json转List,json转对象,json转JSONObject)
      • java 8 List 根据某个字段去重
      • Java List排序
      • 压缩算法:字符串(JSON)压缩和解压【JDK之Deflater压缩与Inflater解压】
      • BCD码是什么?
      • Java二进制、八进制、十进制、十六进制转换
      • Java String字符串 与 ASCII码相互转换
      • 什么是跨域?解决方案有哪些?
      • Java 16进制字符串转10进制
      • LinkedHashMap实现LRU - 附重点源码解析
      • 去掉 if...else 的七种绝佳之法
      • 一眼看清@JSONField注解使用与效果
    • JVM

    • Spring

    • 并发编程

    • Mybatis

    • 网络编程

    • 数据库

    • 缓存

    • 设计模式

    • 分布式

    • 高并发

    • SpringBoot

    • SpringCloudAlibaba

    • Nginx

    • 面试

    • 生产问题

    • 系统设计

    • 消息中间件

    • Java
    • 基础
    程序员子龙
    2024-01-29
    目录

    如何正确遍历删除List中的元素

    删除List中元素这个场景很场景,很多人可能直接在循环中直接去删除元素,这样做对吗?我们就来聊聊。

    # for循环索引删除

    删除长度为4的字符串元素。

        List<String> list = new ArrayList<String>();
        list.add("AA");
        list.add("BBB");
        list.add("CCCC");
        list.add("DDDD");
        list.add("EEE");
    
        for (int i = 0; i < list.size(); i++) {
            if (list.get(i).length() == 4) {
                list.remove(i);
            }
        }
        System.out.println(list);
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14

    实际上输出结果:

    [AA, BBB, DDDD, EEE]
    
    1

    DDDD 竟然没有删掉!

    原因是:删除某个元素后,list的大小size发生了变化,而list的索引也在变化,索引为i的元素删除后,后边元素的索引自动向前补位,即原来索引为i+1的元素,变为了索引为i的元素,但是下一次循环取的索引是i+1,此时你以为取到的是原来索引为i+1的元素,其实取到是原来索引为i+2的元素,所以会导致你在遍历的时候漏掉某些元素。

    比如当你删除第1个元素后,继续根据索引访问第2个元素时,因为删除的关系后面的元素都往前移动了一位,所以实际访问的是第3个元素。不会报出异常,只会出现漏删的情况。

    # foreach循环删除元素

    for (String s : list) {
            if (s.length() == 4) {
                list.remove(s);
    
            }
        }
        System.out.println(list);
    
    1
    2
    3
    4
    5
    6
    7

    如果没有break,会报错:

    java.util.ConcurrentModificationException at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:911) at java.util.ArrayList$Itr.next(ArrayList.java:861) at com.demo.ApplicationTest.testDel(ApplicationTest.java:64) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50) at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12) at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47) at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)

    报ConcurrentModificationException错误的原因:

    看一下JDK源码中ArrayList的remove源码是怎么实现的:

    public boolean remove(Object o) {
            if (o == null) {
                for (int index = 0; index < size; index++)
                    if (elementData[index] == null) {
                        fastRemove(index);
                        return true;
                    }
            } else {
                for (int index = 0; index < size; index++)
                    if (o.equals(elementData[index])) {
                        fastRemove(index);
                        return true;
                    }
            }
            return false;
        }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16

    一般情况下程序会最终调用fastRemove方法:

    private void fastRemove(int index) {
            modCount++;
            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
        }
    
    1
    2
    3
    4
    5
    6
    7
    8

    在fastRemove方法中,可以看到第2行把modCount变量的值加一,但在ArrayList返回的迭代器会做迭代器内部的修改次数检查:

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

    而foreach写法是对实际的Iterable、hasNext、next方法的简写,因为上面的remove(Object)方法修改了modCount的值,所以才会报出并发修改异常。

    阿里开发手册也明确说明禁止使用foreach删除、增加List元素。

    # 迭代器Iterator删除元素

        Iterator<String> iterator = list.iterator();
        while(iterator.hasNext()){
            if(iterator.next().length()==4){
                iterator.remove();
            }
        }
        System.out.println(list);
    
    1
    2
    3
    4
    5
    6
    7

    [AA, BBB, EEE]

    这种方式可以正常的循环及删除。但要注意的是,使用iterator的remove方法,而不是List的remove方法,如果用list的remove方法同样会报上面提到的ConcurrentModificationException错误。

    # 总结

    无论什么场景,都不要对List使用for循环的同时,删除List集合元素,要使用迭代器删除元素。

    上次更新: 2024/01/30, 15:08:57
    Java 动态修改注解值
    什么是Java注解

    ← Java 动态修改注解值 什么是Java注解→

    最近更新
    01
    一个注解,优雅的实现接口幂等性
    11-17
    02
    MySQL事务(超详细!!!)
    10-14
    03
    阿里二面:Kafka中如何保证消息的顺序性?这周被问到两次了
    10-09
    更多文章>
    Theme by Vdoing | Copyright © 2024-2024

        辽ICP备2023001503号-2

    • 跟随系统
    • 浅色模式
    • 深色模式
    • 阅读模式