【问题标题】:Getting a ConcurrentModificationException thrown when removing an element from a java.util.List during list iteration? [duplicate]在列表迭代期间从 java.util.List 中删除元素时引发 ConcurrentModificationException? [复制]
【发布时间】:2011-07-04 00:53:09
【问题描述】:
@Test
public void testListCur(){
    List<String> li=new ArrayList<String>();
    for(int i=0;i<10;i++){
        li.add("str"+i);
    }

    for(String st:li){
        if(st.equalsIgnoreCase("str3"))
            li.remove("str3");
    }
    System.out.println(li);
}

当我运行这段代码时,我会抛出一个ConcurrentModificationException

看起来当我从list 中删除指定元素时,list 不知道它的size 已被更改。

我想知道这是否是collections 和删除元素的常见问题?

【问题讨论】:

    标签: java list concurrentmodification


    【解决方案1】:

    我相信这是 Iterator.remove() 方法背后的目的,能够在迭代时从集合中删除一个元素。

    例如:

    Iterator<String> iter = li.iterator();
    while(iter.hasNext()){
        if(iter.next().equalsIgnoreCase("str3"))
            iter.remove();
    }
    

    【讨论】:

    • 很遗憾,在foreach循环中,无法访问底层的迭代器,所以iterator.remove()被隐藏了。
    • 嗯,是的,你不能使用 for-each 循环,你必须改用 Iterator
    • @afk,是的,您必须切换到使用迭代器。请记住,for-each 循环只是语法糖。这将是使用该语法显然没有意义的情况之一。
    • iter.remove() 也会导致此异常..
    【解决方案2】:

    在没有迭代器的情况下从列表中删除它的 Java 8 方法是:

    li.removeIf(<predicate>)
    

    List<String> li = new ArrayList<String>();
    // ...
    li.removeIf(st -> !st.equalsIgnoreCase("str3"));
    

    【讨论】:

      【解决方案3】:

      请注意,此异常并不总是表示对象已被不同的线程同时修改。如果单个线程发出一系列违反对象约定的方法调用,则该对象可能会抛出此异常。例如,如果一个线程在使用 fail-fast 迭代器迭代集合时直接修改了一个集合,则迭代器将抛出此异常

      取自http://download.oracle.com/javase/1.4.2/docs/api/java/util/ConcurrentModificationException.html

      【讨论】:

        【解决方案4】:

        是的,人们会遇到它——问题是你不能在迭代列表时修改它。我过去使用过 2 种替代方法:

        1. 您可以跟踪要删除的项目的索引,然后在完成迭代后将其删除。
        2. 或者您可以在迭代时将所有要保留的列表复制到一个新列表中,然后在完成后丢弃旧列表。

        这些选项假定您必须遍历列表才能找到要删除的元素——在列表元素是具有您可能测试的属性的复杂对象的情况下很有用。

        在您的特定情况下,您甚至不需要迭代,因为您只需使用 removeAll。查看 API here。还有一些漂亮的方法,比如 retainAll 会丢弃所有不在参数中的东西。只要列表中的对象正确实现等于和哈希码,您就可以使用类似删除/保留的方法。如果您不能依靠 equals/hashcode 来识别应用中实例之间的相等性,则必须自己进行删除....

        【讨论】:

          【解决方案5】:

          试试这个(Java 8):

          list.removeIf(condition);
          

          【讨论】:

            【解决方案6】:

            您可以直接在 for-each 循环中复制要从中删除元素的列表。对我来说,这是最简单的方法。像这样的:

            for (String stringIter : new ArrayList<String>(myList)) {
                myList.remove(itemToRemove);
            }
            

            希望对你有所帮助..

            【讨论】:

              【解决方案7】:

              我觉得值得一提的是Java 8版本

              @Test
              public void testListCur() {
                  List<String> li = new ArrayList<String>();
                  for (int i = 0; i < 10; i++) {
                      li.add("str" + i);
                  }
              
                  li = li.stream().filter(st -> !st.equalsIgnoreCase("str3")).collect(Collectors.toList());
              
                  System.out.println(li);
              }
              

              【讨论】:

              • 或者直接使用li.removeIf...
              【解决方案8】:

              ArrayList 有字段modCount - 集合修改计数

              当您调用方法iterator() 时,会创建新对象Itr。它有字段expectedModCountexpectedModCount 字段由 modCount 值初始化。当你调用

              li.remove("str3");
              

              modCount 递增。你什么时候尝试通过迭代器访问li 检查expectedModCount == modCount

              如果是 false 则抛出 ConcurrentModificationException

              因此,如果您获得迭代器并且在修改集合之后 - 迭代器被认为是无效的并且您不能使用它。

              【讨论】:

                【解决方案9】:

                我认为最好的答案来自 bigdev.de,但我想向它添加一些内容(例如,如果该项目从列表中删除,也许您想在某处或某处记录它):

                List<String> list = new ArrayList<>();
                
                list.removeIf(a -> {
                                boolean condition = a.equalsIgnoreCase("some condition");
                                if(condition)
                                    logger.info("Item removed from the list: " + a);
                                return condition;
                  });
                

                【讨论】:

                  【解决方案10】:

                  我遇到了这个问题,我认为更简单的方法与 hvgotcodes 给出的第二种方法相同。

                  或者您可以在迭代时将所有要保留的列表复制到新列表中,然后在完成后丢弃旧列表。

                  @Test
                  public void testListCur(){
                      List<String> li=new ArrayList<String>();
                      for(int i=0;i<10;i++){
                          li.add("str"+i);
                      }
                      List<String> finalLi = new ArrayList<String>();
                      for(String st:li){
                          if(st.equalsIgnoreCase("str3")){
                              // Do nothing
                          } else {
                              finalLi.add(st);
                          }
                      }
                      System.out.println(finalLi);
                  }
                  

                  【讨论】:

                    【解决方案11】:

                    我以不同的方式循环...

                    public void testListCur(){
                        List<String> li=new ArrayList<String>();
                        for(int i=0;i<10;i++){
                            li.add("str"+i);
                        }
                    
                        for(int i=0; i<li.size(); i++)
                            if(li.get(i).equalsIgnoreCase("str3"))
                                li.remove(i--);
                    
                        System.out.println(li);
                    }
                    

                    【讨论】:

                      猜你喜欢
                      • 1970-01-01
                      • 2013-08-29
                      • 2018-05-09
                      • 2019-09-11
                      • 2020-02-07
                      • 2013-01-29
                      • 1970-01-01
                      • 2013-01-23
                      相关资源
                      最近更新 更多