欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 文旅 > 游戏 > Java如何在遍历集合时删除特定元素

Java如何在遍历集合时删除特定元素

2025/4/22 4:06:54 来源:https://blog.csdn.net/duke_ding2/article/details/146171014  浏览:    关键词:Java如何在遍历集合时删除特定元素

文章目录

  • 环境
  • 问题
  • 分析
  • 解决办法
    • Iterator
    • for循环
    • removeIf()方法
  • 其它
    • Set
      • Iterator
      • removeIf()方法
    • Map
      • Iterator
      • removeIf()方法
  • 总结

环境

  • Ubuntu 24.04.1
  • Java 21

问题

创建一个List对象如下:

        List<String> list = new ArrayList<>();list.add("aaa");list.add("bbb");list.add("ccc");list.add("ddd");list.add("eee");

现在要遍历list,移除其中的 ccc 元素。

        for (String s : list)if (s.equals("ccc"))list.remove(s);

运行代码,报错如下:

Exception in thread "main" java.util.ConcurrentModificationExceptionat java.base/java.util.ArrayList$Itr.checkForComodification(ArrayList.java:1095)at java.base/java.util.ArrayList$Itr.next(ArrayList.java:1049)at org.example.Test0313.main(Test0313.java:25)

这里,抛出了 ConcurrentModificationException (注意这个异常是在遍历(即for循环处)时抛出的,而不是在remove时抛出的)。

然而,神奇的是,如果删除的是list里倒数第2个元素(本例是 "ddd"),则不会报错:

        for (String s : list) {if (s.equals("ddd"))list.remove(s);
//            System.out.println(s);}

能够正确的删除 "ddd"

本例中list里共有5个元素,经测试,如果是其它数量,也是只有在删除倒数第2个元素时,不会抛出异常。

分析

首先需要强调的是,要遍历List并删除特定的元素,不要用 for-each 循环的方式,否则绝大部分情况下都会抛出 ConcurrentModificationException 异常。删除倒数第2个元素时不报错,只是一个特例。

简而言之,for-each循环,强调的是“遍历”,而不是“增删改”(改还好,主要是增删)。这一点和Stream倒有点类似。

注:跑个题,Stream的 toList() 方法,会将Stream收集为一个“不可变List”,如果尝试添加或删除元素,会抛出 UnsupportedOperationException 异常,具体可参见我领一篇文档( https://blog.csdn.net/duke_ding2/article/details/143888158 )。

所以,实际应用时,只需记得:若要增删元素,则不要使用 for-each 循环的方式。

那么,到底为什么删除倒数第2个元素时不报错呢?

for-each循环本质上是使用迭代器(Iterator)来实现的。在迭代过程中,迭代器内部维护了一个 expectedModCount 变量,它记录了迭代器创建时列表的修改次数;而列表本身也有一个 modCount 变量,用于记录列表结构的修改次数。每次调用迭代器的 next() 方法时,都会检查 expectedModCountmodCount 是否相等,如果二者不相等,就会抛出 ConcurrentModificationException 异常。

所以,问题的关键点就在于Iterator的 next() 方法。显然,在删除倒数第2个元素后,并没有调用 next() 方法,所以不报错。

那么问题又来了,为什么在删除倒数第2个元素后,并没有调用 next() 方法;而在删除最后一个元素后,反而会调用 next() 方法呢(都遍历完了还next啥呢)?

判断是否需要调用Iterator的 next() 方法的条件是:Iterator的 hasNext() 方法返回true。

Iterator在判断 hasNext() 时,是通过List大小来判断的,遍历到倒数第2个元素并将其删除后,已遍历的元素数量和List的大小相等,Iterator会认为已经遍历结束了,所以 hasNext() 方法会返回false。

同理,遍历到倒数最后一个元素并将其删除后,已遍历的元素数量和List的大小并不相等,Iterator会认为已经遍历尚未结束,所以 hasNext() 方法会返回true。

这就有点扯淡了,已遍历的元素数量都超过List大小了,还觉得需要继续遍历呢?

Anyway,可能Java就是这么设计的:前面已经提到,不要这么做,否则会出问题,而你非要这么做,那不管出不出问题的,反正就是你的用法不对。 😃

总结:在for-each循环里删除元素会报错(特例:删除倒数第2个元素不报错),所以不要这么做。

解决办法

Iterator

前面是隐式使用Iterator,实际上,显式使用Iterator时,可以直接安全的通过其 remove() 方法来删除其当前指向元素:

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

注意:使用Iterator时,不要通过List来删除元素(参见注释处代码),否则会遇到相同的问题(删除倒数第2个元素是OK的,但删除其它元素后,随后的 next() 方法会报错)。

for循环

这里又有个坑:

        for (int i = 0; i < list.size(); i++) {var str = list.get(i);if (str.equals("ccc"))list.remove(str);}

看上去似乎一切OK,确实把 ccc 删除了。但是,这种方法有个潜在的问题:删除某个元素后,下一次迭代会跳过一个元素。这是因为删除元素后,其后的元素位置已经变化了,通过 list.get(i) 获取下一个元素时,实际上获取的是下下一个元素(如果没想明白,单步调试一下就清楚了)。所以,这种方法只适用于删除一个元素的情况(当然,最好是压根就别用这种方法)。

正确方法是使用逆序遍历:

        for (int i = list.size() - 1; i >= 0; i--) {var str = list.get(i);if (str.equals("ccc"))list.remove(str);}

使用逆序遍历,即使删除元素,也不影响后续的遍历(可保证每个元素遍历一次)。

removeIf()方法

        list.removeIf(e -> e.equals("ccc") || e.equals("ddd"));

注意:该方法简单又方便,推荐使用。

其它

List是有序集合,可通过位置来遍历,而对于Map、Set等无序集合,不能通过位置来遍历,只能通过Iterator或者for-each循环遍历。前面提到,不要在for-each循环里删除元素(当然可以变通一下,用for-each来遍历集合,然后重新生成一个满足需求的集合)。所以,一般用Iterator或者 removeIf() 方法来删除元素。(注:Map本身没有 removeIf() 方法,不过其entrySet和keySet都有 removeIf() 方法)。

Set

创建Set对象如下:

        Set<String> set = new HashSet<>();set.add("aaa");set.add("bbb");set.add("ccc");set.add("ddd");set.add("eee");

Iterator

使用Iterator来遍历并删除特定元素:

        Iterator<String> iterator = set.iterator();while (iterator.hasNext()) {var str = iterator.next();if (str.equals("eee")) {iterator.remove();
//                set.remove(str);}}

注意:同理,不要在遍历时,使用Set的 remove() 方法删除元素(参见注释处代码),否则随后的 next() 方法会报错。同样,也是在删除倒数第2个元素时不报错,只不过Set是无序的,哪个元素是倒数第2个元素,取决于Set内部实现。

removeIf()方法

        set.removeIf(str -> str.equals("bbb"));

Map

创建Map对象如下:

        Map<String, String> map = new HashMap<>();map.put("a", "aaa");map.put("b", "bbb");map.put("c", "ccc");map.put("d", "ddd");map.put("e", "eee");

Iterator

使用Iterator来遍历并删除特定key值:

        Iterator<Map.Entry<String, String>> iterator = map.entrySet().iterator();while (iterator.hasNext()) {Map.Entry<String, String> entry = iterator.next();if (entry.getKey().equals("d")) {iterator.remove();
//                map.remove(entry.getKey());}}

或者:

        Iterator<String> iterator = map.keySet().iterator();while (iterator.hasNext()) {String key = iterator.next();if (key.equals("c")) {iterator.remove();
//                map.remove(key);}}

同理,不要在遍历时,使用Map的 remove() 方法删除key值(参见注释处代码),否则随后的 next() 方法会报错。同样,也是在删除倒数第2个key值时不报错,只不过keySet是无序的,哪个key值是倒数第2个key值,取决于keySet内部实现。

removeIf()方法

        map.entrySet().removeIf(entry -> entry.getKey().equals("d"));

或者:

        map.keySet().removeIf(key -> key.equals("c"));

总结

对于List、Set、Map:

  • 不要在for-each循环里删除元素,否则会抛出 ConcurrentModificationException 异常(例外:删除倒数第2个元素时不报错)
  • 推荐使用 removeIf() 方法来删除特定元素
  • 也可以使用Iterator来遍历集合,并使用Iterator的 remove() 方法来删除元素
  • 不要在使用Iterator遍历集合时,使用集合的 remove() 方法来删除元素(和第一条同理,实际上第一条就是隐含使用了Iterator)
  • 对于List,也可以用for循环,通过位置来删除元素,不过要注意需要倒序遍历List

注意:发生异常时,不是在删除元素处,而是在Iterator的 next() 处。

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

热搜词