List 集合遍历过程中删除元素。这个坑踩一遍就不要再踩了!

简介: List 集合遍历过程中删除元素。这个坑踩一遍就不要再踩了!

作为一名后端开发,不管采用什么语言 使用 List 集合的频率都非常高。


对 List 集合的遍历和遍历中操作数据也是家常便饭。


我从我使用的过程中对于此问题的思考与实践形成记录,与大家交流。有不对的地方,恳请大家指正。


1. 遍历


List 集合的遍历有很多种方式,每一种遍历方式只有性能上的差异,不会有异常和暗坑。这里不再赘述。


2. 遍历过程中删除元素


因为 List 集合有多种遍历方式,也就意味着存在多种遍历中删除的方式:


在做各种方式比对前,我们先加入一些初始数据。


 List<String> list = new ArrayList<>();
 list.add("snow");
 list.add("nier");
 list.add("sar");
 list.add("juaya");
 list.add("rock");
 list.add("snow");
 list.add("nier");


2.1 for 简单循环正向遍历方式


for (int i = 0; i < list.size(); i++) {
  String name = list.get(i);
  if(name.equals("snow")){
    list.remove(i);
  }
  if(name.equals("nier")){
    list.remove(i);
  }
}
System.out.println(list);


上述代码打印结果:


[nier, sar, juaya, rock, nier]


很明显 这不是正确的结果。


这是 因为:在遍历过程中,删除或者增加元素后,集合长度会因为实时改动而改动,也就是说集合在遍历过程中忘了初心了。


举个栗子:现在对长度为 5 的集合进行遍历,在遍历到 第三个元素的时候删除了ta,那么此时集合长度成了4,那么此时集合就不会遍历到之前下标为 5 的元素了。


知道了原因后,我们可以对症下药,达到我们的预期。修改代码如下:


for (int i = 0; i < list.size(); i++) {
  String name = list.get(i);
  if(name.equals("snow")){
    list.remove(i);
    //  加补偿机制
    i--;
  }
  if(name.equals("nier")){
    list.remove(i);
    //  加补偿机制
    i--;
  }
}
System.out.println(list);


上述代码执行结果:


[sar, juaya, rock]


如此 加入了补偿机制后虽然达到了预期结果。但是很明显代码不够优雅。So 这种方式不推荐。


2.2 for 简单循环反向遍历方式


反向遍历和正向遍历类似,只不过是反方向的钟而已。


for (int i = list.size() - 1; i >= 0; i--) {
  String name = list.get(i);
  if(name.equals("snow")){
    list.remove(i);
  }
  if(name.equals("nier")){
    list.remove(i);
  }
}
System.out.println(list);


上述代码执行结果:


[sar, juaya, rock]


咦~可以哎。结果正确。那这种方式是不是就绝对可以了呢。 NO!


逆序遍历虽然在这个场景下达到了预期的正确结果,但是因为 这种方式和正序在原理上是相同的。所以在遍历过程中操作元素也会有其劣根性。比如在逆序遍历过程中加入元素,依然会对整个遍历产生影响。 所以这也不是推荐的方式。


2.3 foreach 方式遍历删除


单从遍历来看 这种方式比上述两种方式优雅了不少。但是遍历过程中操作元素能达到预期结果吗?


for (String s : list) {
  if(s.equals("rock")){
    list.remove("rock");
  }
}


执行结果…



原因: 这种循环方式,其生成的字节码其实使用的 Iterator ,使用的核心方法是 `hasnext()` 和 `next()`。 然后再来看下ArrayList类的 Iterator 是如何实现的呢?


Iterator 部分源码:



可以看出:调用next()方法获取下一个元素时,第一行代码就是调用了checkForComodification();


而该方法的核心逻辑就是比较 modCount 和 expectedModCount 这2个变量的值。

在上面的例子中,刚开始 modCount 和 expectedModCount 的值都为 n,所以第1次获取元素 “rock” 是没问题的,但是当执行完下面这行代码时:


list.remove("rock");


modCount 的值就被修改了。



So: 使用 foreach 遍历过程中删除元素是不可行的。


2.4 Iterator的remove()方法


Iterator<String> iterator = list.iterator();
while (iterator.hasNext()){
    String next = iterator.next();
    if("snow".equals(next)){
        iterator.remove();
    }
}


执行结果:


[nier, sar, juaya, rock, nier]


那这种方式怎么就可以删除了呢???


因为:


这种方式每次删除一个元素,都会将modCount 的值重新赋值给 expectedModCount,这样2个变量就相等了,不会触发 java.util.ConcurrentModificationException 异常。



虽然这种方式符合了业务要求,但是还是不推荐这种方式,因为他的写法依然不够优雅。


2.5 removeIf() (推荐)


从JDK1.8开始,可以使用 removeIf() 方法来代替 Iterator的remove()方法实现一边遍历一边删除。


list.removeIf(s -> s.equals("snow"));
System.out.println(list);


打印结果:


[nier, sar, juaya, rock, nier]


其源码如下:



2.6 Strem 方式


list = list.stream().filter( name -> !("snow".equals(name) ) ).collect(Collectors.toList());
System.out.println(list);
list = list.


filter 过滤会保留满足条件的。


结果如下:


[nier, sar, juaya, rock, nier]
相关文章
|
4月前
|
存储 安全 Java
【Java集合类面试二十五】、有哪些线程安全的List?
线程安全的List包括Vector、Collections.SynchronizedList和CopyOnWriteArrayList,其中CopyOnWriteArrayList通过复制底层数组实现写操作,提供了最优的线程安全性能。
|
4月前
|
安全
List集合特有功能
List集合特有功能
43 2
|
4月前
|
Java
【Java集合类面试二十三】、List和Set有什么区别?
List和Set的主要区别在于List是一个有序且允许元素重复的集合,而Set是一个无序且元素不重复的集合。
|
4月前
|
存储 Java
Java学习笔记 List集合的定义、集合的遍历、迭代器的使用
Java学习笔记 List集合的定义、集合的遍历、迭代器的使用
|
11天前
|
Java 机器人 程序员
从入门到精通:五种 List 遍历方法对比与实战指南
小米是一位热爱分享技术的程序员,本文详细介绍了 Java 中遍历 List 的五种方式:经典 for 循环、增强 for 循环、Iterator 和 ListIterator、Stream API 以及 forEach 方法。每种方式都有其适用场景和优缺点,例如 for 循环适合频繁访问索引,增强 for 循环和 forEach 方法代码简洁,Stream API 适合大数据量操作,ListIterator 支持双向遍历。文章通过生动的小故事和代码示例,帮助读者更好地理解和选择合适的遍历方式。
27 2
|
2月前
|
安全 Java 程序员
深入Java集合框架:解密List的Fail-Fast与Fail-Safe机制
本文介绍了 Java 中 List 的遍历和删除操作,重点讨论了快速失败(fail-fast)和安全失败(fail-safe)机制。通过普通 for 循环、迭代器和 foreach 循环的对比,详细解释了各种方法的优缺点及适用场景,特别是在多线程环境下的表现。最后推荐了适合高并发场景的 fail-safe 容器,如 CopyOnWriteArrayList 和 ConcurrentHashMap。
69 5
|
2月前
|
Java 程序员 编译器
Java|如何正确地在遍历 List 时删除元素
从源码分析如何正确地在遍历 List 时删除元素。为什么有的写法会导致异常,而另一些不会。
42 3
|
1月前
|
NoSQL Java Redis
List集合按照由小到大排序或者由大到小排序
List集合按照由小到大排序或者由大到小排序
45 0
|
2月前
|
存储 分布式计算 NoSQL
大数据-40 Redis 类型集合 string list set sorted hash 指令列表 执行结果 附截图
大数据-40 Redis 类型集合 string list set sorted hash 指令列表 执行结果 附截图
29 3
|
3月前
|
NoSQL Java Redis
List集合按照由小到大排序或者由大到小排序
List集合按照由小到大排序或者由大到小排序
26 3