foreach循环中remove操作 (什么是fail-fast?)

先来看看下面的三段代码,你觉得有哪几个会报错,那几个会正确输出list

java 复制代码
List<String> list = new ArrayList<>();
        list.add("2");
        list.add("2");
        for(String str:list){
            if("2".equals(str)){
                list.remove(str);
            }
        }
        System.out.println(list);
java 复制代码
List<String> list = new ArrayList<>();
        list.add("2");
        list.add("2");
        list.add("3");
        for(String str:list){
            if("2".equals(str)){
                list.remove(str);
            }
        }
        System.out.println(list);
java 复制代码
List<String> list = new ArrayList<>();
        list.add("1");
        list.add("2");
        list.add("3");
        for(String str:list){
            if("2".equals(str)){
                list.remove(str);
            }
        }
        System.out.println(list);
    }

先公布答案 上面上段代码执行结果分别是: [2]

Exception 复制代码
	at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:911)
	at java.util.ArrayList$Itr.next(ArrayList.java:861)
	at com.xmz.test.Test.main(Test.java:16)

[1, 3]

先说正确规范

在阿里巴巴手册中明确规定了,不要在foreach中使用remove\add操作。

【问题】为什么上面代码1,3没问题?2会抛异常

异常的原因 : 因为增强for循环是根据迭代器遍历的,在循环中修改集合大小,会导致迭代器中的数据不一致,可能导致错误。 Java中有个fail-fast错误检测机制,一旦检测到可能发生错误,就会立马抛出异常,程序不能继续执行下去。 1和3为什么不报异常? 因为在某些特殊情况下,list.remove(str)可以在不触发fail-fast机制的情况下删除元素。 例如在上面的第一、三两个代码中,我们删除的是列表中的倒数第二个元素,那么就不会抛出异常,因为这种情况下,删除了元素,同时也改变了列表的大小,使得列表的大小和当前的cursor的大小一样,因此就不会执行checkForComodification()方法,也就不会抛出异常。

【方案】怎么在集合中正确使用remove\add操作

方案一、使用普通for循环进行操作

java 复制代码
 List<String> list = new ArrayList<>();
        list.add("2");
        list.add("2");
        list.add("3");
        for (int i = 0; i < list.size(); i++) {
            if("2".equals(list.get(i))){
                list.remove(list.get(i));
            }
        }
        System.out.println(list);

但是这种方案在remove的时候,会存在漏删的情况。 输出结果:[2, 3]

方案二、使用Iterator提供的remove方法。

这也就是在阿里巴巴开发手册中的方法

java 复制代码
List<String> list = new ArrayList<>();
        list.add("2");
        list.add("2");
        list.add("3");
        Iterator<String> iterator = list.iterator();
        while (iterator.hasNext()){
            String str = iterator.next();
            if("2".equals(str)){
                iterator.remove();
            }
        }
        System.out.println(list);

输出结果:[3] 这是最正确的方案

方案三、使用filter过滤

java 复制代码
List<String> list = new ArrayList<>();
        list.add("2");
        list.add("2");
        list.add("3");
        list = list.stream().filter(str -> !"2".equals(str)).collect(Collectors.toList());
        System.out.println(list);

输出结果:[3]

方案四、使用增强for循环也可以,但是每次要break

如果这个集合中只有一个我们要删除的元素,就可以使用这种方案,像set集合,如果list集合里面有多个要删除的元素,最终只能成功删除第一个

java 复制代码
List<String> list = new ArrayList<>();
        list.add("1");
        list.add("2");
        list.add("3");
        for(String str:list){
            if("1".equals(str)){
                list.remove(str);
                break;
            }
        }
        System.out.println(list);

输出结果:[2, 3]

方案五、使用fail-fast集合类

在Java中,有一些fail-fast机制的集合类。这样的集合容器在遍历时不是直接在集合内容上访问的,而是先复制原有集合内容,在拷贝的集合上进行遍历。 由于在迭代时是对原集合的拷贝进行遍历,所以在遍历过程中对原集合所作的修改并不能背迭代器检测到。

java 复制代码
        ConcurrentLinkedDeque<String> concurrentLinkedDeque = new ConcurrentLinkedDeque<>();
        concurrentLinkedDeque.add("2");
        concurrentLinkedDeque.add("2");
        concurrentLinkedDeque.add("3");
        for(String str:concurrentLinkedDeque){
            if("2".equals(str)){
                concurrentLinkedDeque.remove(str);
            }
        }
        System.out.println(concurrentLinkedDeque);

参考文章

参考文章:什么是fail-fast

相关推荐
酉鬼女又兒2 分钟前
JavaLeetCode 第一题「两数之和」:从暴力枚举到一遍哈希表的正确与错误实现,详解HashMap核心知识点及常见陷阱
java·开发语言·数据结构·算法·leetcode·职场和发展·散列表
JackSparrow4143 分钟前
彻底理解Java NIO(一)C语言实现 单进程+多进程+多线程 阻塞式I/O 服务器详解
java·linux·c语言·网络·后端·tcp/ip·nio
小江的记录本5 分钟前
【微服务与云原生架构】Serverless架构、FaaS/BaaS、核心原理、优缺点
java·后端·微服务·云原生·架构·系统架构·serverless
谢谢 啊sir7 分钟前
L2-060 大语言模型的推理 - java
java·人工智能·语言模型
下地种菜小叶13 分钟前
特征定义、特征计算、特征服务怎么配合?一次讲透
java·服务器·前端·数据库·spring cloud
lifewange20 分钟前
Idea如何调大字体
java·macos·intellij-idea
许彰午24 分钟前
# 一个Java老鸟的TensorFlow入门——从计算图到GradientTape
java·tensorflow·neo4j
itzixiao25 分钟前
L1-055 谁是赢家(10 分)[java][python]
java·python·算法
IT利刃出鞘25 分钟前
Java反射--PropertyDescriptor的使用
java·开发语言
所愿ღ38 分钟前
SSM框架-Spring1
java·开发语言·笔记·spring