【问题标题】:How to avoid java.util.ConcurrentModificationException when iterating through and removing elements from an ArrayList迭代并从 ArrayList 中删除元素时如何避免 java.util.ConcurrentModificationException
【发布时间】:2011-12-27 15:05:37
【问题描述】:

我有一个要迭代的 ArrayList。在迭代它时,我必须同时删除元素。显然这会引发java.util.ConcurrentModificationException

处理此问题的最佳做法是什么?我应该先克隆列表吗?

我删除的不是循环本身的元素,而是代码的另一部分。

我的代码如下所示:

public class Test() {
    private ArrayList<A> abc = new ArrayList<A>();

    public void doStuff() {
        for (A a : abc) 
        a.doSomething();
    }

    public void removeA(A a) {
        abc.remove(a);
    }
}

a.doSomething 可能会调用Test.removeA()

【问题讨论】:

标签: java arraylist foreach


【解决方案1】:

两种选择:

  • 创建一个要删除的值列表,添加到循环中的该列表,然后在最后调用originalList.removeAll(valuesToRemove)
  • 在迭代器本身上使用remove() 方法。请注意,这意味着您不能使用增强的 for 循环。

作为第二个选项的示例,从列表中删除任何长度大于 5 的字符串:

List<String> list = new ArrayList<String>();
...
for (Iterator<String> iterator = list.iterator(); iterator.hasNext(); ) {
    String value = iterator.next();
    if (value.length() > 5) {
        iterator.remove();
    }
}

【讨论】:

  • 我应该提到我删除了代码另一部分中的元素,而不是循环本身。
  • @Roflcoptr:如果不看这两段代码是如何交互的,就很难回答。基本上,你不能那样做。首先克隆列表是否会有所帮助并不明显,因为没有看到它是如何连接在一起的。你能在你的问题中提供更多细节吗?
  • 我知道克隆列表会有所帮助,但我不知道这是否是一个好方法。但我会添加更多代码。
  • 这个解决方案也会导致java.util.ConcurrentModificationException,见stackoverflow.com/a/18448699/2914140
  • @CoolMind:没有多线程,这段代码应该没问题。
【解决方案2】:

来自 ArrayList 的 JavaDocs

这个类的迭代器和listIterator返回的迭代器 方法是快速失败的:如果列表在任何结构上被修改 迭代器创建后的时间,除了通过 迭代器自己的删除或添加方法,迭代器会抛出一个 ConcurrentModificationException。

【讨论】:

  • 问题的答案在哪里?
  • 就像它说的,除了通过迭代器自己的删除或添加方法
【解决方案3】:

您正在尝试从高级“for 循环”中的列表中删除值,这是不可能的,即使您应用了任何技巧(您在代码中做了)。 更好的方法是按照此处的其他建议编写迭代器级别。

我想知道人们怎么没有建议传统的 for 循环方法。

for( int i = 0; i < lStringList.size(); i++ )
{
    String lValue = lStringList.get( i );
    if(lValue.equals("_Not_Required"))
    {
         lStringList.remove(lValue);
         i--; 
    }  
}

这也有效。

【讨论】:

  • 这是不正确的!!!当您删除一个元素时,下一个元素将占据其位置,而当 i 增加时,下一个元素不会在下一次迭代中检查。在这种情况下,您应该使用 for(int i = lStringList.size(); i>-1; i-- )
  • 同意!另一种方法是执行 i--;在 for 循环中的 if 条件中。
  • 我认为这个答案已经过编辑以解决上述 cmets 中的问题,所以现在它工作正常,至少对我来说是这样。
  • @KiraResari,是的。我已经更新了解决问题的答案。
【解决方案4】:

在 Java 8 中,您可以使用 Collection 接口并通过调用 removeIf 方法来做到这一点:

yourList.removeIf((A a) -> a.value == 2);

更多信息可以找到here

【讨论】:

    【解决方案5】:

    你真的应该以传统方式迭代数组

    每次从列表中删除一个元素,后面的元素都会被向前推。只要您不更改迭代元素以外的元素,以下代码应该可以工作。

    public class Test(){
        private ArrayList<A> abc = new ArrayList<A>();
    
        public void doStuff(){
            for(int i = (abc.size() - 1); i >= 0; i--) 
                abc.get(i).doSomething();
        }
    
        public void removeA(A a){
            abc.remove(a);
        }
    }
    

    【讨论】:

      【解决方案6】:

      在迭代列表时,如果要删除元素是可能的。让我们看看下面我的例子,

      ArrayList<String>  names = new ArrayList<String>();
              names.add("abc");
              names.add("def");
              names.add("ghi");
              names.add("xyz");
      

      我有上面的数组列表名称。我想从上面的列表中删除“def”名称,

      for(String name : names){
          if(name.equals("def")){
              names.remove("def");
          }
      }
      

      上面的代码抛出 ConcurrentModificationException 异常,因为您在迭代时正在修改列表。

      因此,要通过这种方式从 Arraylist 中删除“def”名称,

      Iterator<String> itr = names.iterator();            
      while(itr.hasNext()){
          String name = itr.next();
          if(name.equals("def")){
              itr.remove();
          }
      }
      

      上面的代码,通过迭代器我们可以从Arraylist中去掉“def”的名字,然后尝试打印数组,你会看到下面的输出。

      输出:[abc, ghi, xyz]

      【讨论】:

      • 另外,我们可以使用并发包中提供的并发列表,以便在迭代时进行删除和添加操作。例如看下面的代码 sn-p。 ArrayList 名称 = 新 ArrayList(); CopyOnWriteArrayList copyNames = new CopyOnWriteArrayList(names); for(String name : copyNames){ if(name.equals("def")){ copyNames.remove("def"); } }
      • CopyOnWriteArrayList 将是最昂贵的操作。
      【解决方案7】:

      按正常方式循环,java.util.ConcurrentModificationException 是与访问的元素有关的错误。

      那就试试吧:

      for(int i = 0; i < list.size(); i++){
          lista.get(i).action();
      }
      

      【讨论】:

      • 您没有从列表中删除任何内容,从而避免了java.util.ConcurrentModificationException。棘手。 :) 您不能真正将其称为“常规方式”来迭代列表。
      【解决方案8】:

      这是一个示例,我使用不同的列表添加要删除的对象,然后我使用 stream.foreach 从原始列表中删除元素:

      private ObservableList<CustomerTableEntry> customersTableViewItems = FXCollections.observableArrayList();
      ...
      private void removeOutdatedRowsElementsFromCustomerView()
      {
          ObjectProperty<TimeStamp> currentTimestamp = new SimpleObjectProperty<>(TimeStamp.getCurrentTime());
          long diff;
          long diffSeconds;
          List<Object> objectsToRemove = new ArrayList<>();
          for(CustomerTableEntry item: customersTableViewItems) {
              diff = currentTimestamp.getValue().getTime() - item.timestamp.getValue().getTime();
              diffSeconds = diff / 1000 % 60;
              if(diffSeconds > 10) {
                  // Element has been idle for too long, meaning no communication, hence remove it
                  System.out.printf("- Idle element [%s] - will be removed\n", item.getUserName());
                  objectsToRemove.add(item);
              }
          }
          objectsToRemove.stream().forEach(o -> customersTableViewItems.remove(o));
      }
      

      【讨论】:

      • 我认为您在执行两个循环时会做额外的工作,在最坏的情况下,循环将是整个列表。只在一个循环中完成它会最简单且成本更低。
      • 我不认为你可以从第一个循环中删除对象,因此需要额外的删除循环,删除循环也只是要删除的对象 - 也许你可以写一个只有一个循环的例子,我想看看 - 谢谢@LuisCarlos
      • 正如你所说的这段代码,你不能删除 for 循环内的任何元素,因为它会导致 java.util.ConcurrentModificationException 异常。但是,您可以使用基本的 for。在这里,我使用您的部分代码编写了一个示例。
      • for(int i = 0; i 10) { customersTableViewItems.remove(i--); } } 很重要——因为你不想跳过任何元素。您也可以使用 ArrayList 类提供的方法 removeIf(Predicate super E> filter) 。希望对您有所帮助
      • 发生异常是因为在 for 循环中作为对列表迭代器的活动引用。在正常情况下,没有参考,您可以更灵活地更改数据。希望对您有所帮助
      【解决方案9】:

      一种选择是将removeA 方法修改为此-

      public void removeA(A a,Iterator<A> iterator) {
           iterator.remove(a);
           }
      

      但这意味着您的doSomething() 应该能够将iterator 传递给remove 方法。不是一个好主意。

      你能分两步做到这一点吗: 在迭代列表的第一个循环中,不是删除选定的元素,而是将它们标记要删除。为此,您可以简单地将这些元素(浅拷贝)复制到另一个List

      然后,一旦您的迭代完成,只需从第一个列表中的第二个列表中的所有元素执行 removeAll

      【讨论】:

      • 太好了,我使用了相同的方法,尽管我循环了两次。它使事情变得简单,并且没有并发问题:)
      • 我没有看到 Iterator 有一个 remove(a) 方法。 remove() 不接受任何参数 docs.oracle.com/javase/8/docs/api/java/util/Iterator.html 我错过了什么?
      • @c0der 是对的。我的意思是这怎么会被投票 5 次...
      【解决方案10】:

      不要使用 For each 循环,而是使用普通的 for 循环。例如,下面的代码删除了数组列表中的所有元素,而不给出 java.util.ConcurrentModificationException。您可以根据自己的用例修改循环中的条件。

      for(int i=0; i<abc.size(); i++)  {
             e.remove(i);
       }
      

      【讨论】:

        【解决方案11】:

        像这样做一些简单的事情:

        for (Object object: (ArrayList<String>) list.clone()) {
            list.remove(object);
        }
        

        【讨论】:

          【解决方案12】:

          在我的情况下,接受的答案不起作用,它会停止异常,但会导致我的列表出现一些不一致。以下解决方案非常适合我。

          List<String> list = new ArrayList<>();
          List<String> itemsToRemove = new ArrayList<>();
          
          for (String value: list) {
             if (value.length() > 5) { // your condition
                 itemsToRemove.add(value);
             }
          }
          list.removeAll(itemsToRemove);
          

          在这段代码中,我在另一个列表中添加了要删除的项目,然后使用list.removeAll 方法删除所有必需的项目。

          【讨论】:

            【解决方案13】:

            另一种使用流的 Java 8 解决方案:

                    theList = theList.stream()
                        .filter(element -> !shouldBeRemoved(element))
                        .collect(Collectors.toList());
            

            在 Java 7 中,您可以改用 Guava:

                    theList = FluentIterable.from(theList)
                        .filter(new Predicate<String>() {
                            @Override
                            public boolean apply(String element) {
                                return !shouldBeRemoved(element);
                            }
                        })
                        .toImmutableList();
            

            请注意,Guava 示例会生成一个不可变列表,它可能是也可能不是您想要的。

            【讨论】:

              【解决方案14】:

              您也可以使用 CopyOnWriteArrayList 代替 ArrayList。这是从 JDK 1.5 开始的最新推荐方法。

              【讨论】:

                【解决方案15】:
                for (A a : new ArrayList<>(abc)) {
                    a.doSomething();
                    abc.remove(a);
                }
                

                【讨论】:

                  【解决方案16】:

                  有时老学校是最好的。只需进行一个简单的 for 循环,但请确保从列表末尾开始,否则当您删除项目时,您将与索引不同步。

                  List<String> list = new ArrayList<>();
                  for (int i = list.size() - 1; i >= 0; i--) {
                    if ("removeMe".equals(list.get(i))) {
                      list.remove(i);
                    }
                  }
                  

                  【讨论】:

                    【解决方案17】:

                    “我应该先克隆列表吗?”

                    这将是最简单的解决方案,从克隆中删除,并在删除后将克隆复制回来。

                    我的 rummikub 游戏中的一个例子:

                    SuppressWarnings("unchecked")
                    public void removeStones() {
                      ArrayList<Stone> clone = (ArrayList<Stone>) stones.clone();
                      // remove the stones moved to the table
                      for (Stone stone : stones) {
                          if (stone.isOnTable()) {
                             clone.remove(stone);
                          }
                      }
                      stones = (ArrayList<Stone>) clone.clone();
                      sortStones();
                    }
                    

                    【讨论】:

                    • 投反对票的人至少应该在投反对票之前发表评论。
                    • 这个答案本质上没有任何问题,也许stones = (...) clone.clone(); 是多余的。 stones = clone; 不会做同样的事情吗?
                    • 我同意,不需要第二次克隆。您可以通过迭代克隆来进一步简化这一点,并直接从stones 中删除元素。这样你甚至不需要clone 变量:for (Stone stone : (ArrayList&lt;Stone&gt;) stones.clone()) {...
                    【解决方案18】:

                    我知道我来晚了,但我回答这个问题是因为我认为这个解决方案简单而优雅:

                    List<String> listFixed = new ArrayList<String>();
                    List<String> dynamicList = new ArrayList<String>();
                    
                    public void fillingList() {
                        listFixed.add("Andrea");
                        listFixed.add("Susana");
                        listFixed.add("Oscar");
                        listFixed.add("Valeria");
                        listFixed.add("Kathy");
                        listFixed.add("Laura");
                        listFixed.add("Ana");
                        listFixed.add("Becker");
                        listFixed.add("Abraham");
                        dynamicList.addAll(listFixed);
                    }
                    
                    public void updatingListFixed() {
                        for (String newList : dynamicList) {
                            if (!listFixed.contains(newList)) {
                                listFixed.add(newList);
                            }
                        }
                    
                        //this is for add elements if you want eraser also 
                    
                        String removeRegister="";
                        for (String fixedList : listFixed) {
                            if (!dynamicList.contains(fixedList)) {
                                removeResgister = fixedList;
                            }
                        }
                        fixedList.remove(removeRegister);
                    }
                    

                    所有这些都是为了从一个列表更新到另一个列表,您可以从一个列表中完成所有操作 并在方法更新中检查两个列表,可以擦除或在列表之间添加元素。 这意味着两个列表的大小始终相同

                    【讨论】:

                      【解决方案19】:

                      使用迭代器代替数组列表

                      将集合转换为类型匹配的迭代器

                      然后移动到下一个元素并移除

                      Iterator<Insured> itr = insuredSet.iterator();
                      while (itr.hasNext()) { 
                          itr.next();
                          itr.remove();
                      }
                      

                      移动到下一个在这里很重要,因为它应该使用索引来删除元素。

                      【讨论】:

                        【解决方案20】:

                        如果您的目标是从列表中删除所有元素,您可以遍历每个项目,然后调用:

                        list.clear()
                        

                        【讨论】:

                          【解决方案21】:

                          怎么样

                          import java.util.Collections;
                          
                          List<A> abc = Collections.synchronizedList(new ArrayList<>());
                          

                          【讨论】:

                            【解决方案22】:

                            错误

                            当我将元素添加到同一个列表时出现错误:

                            fun <T> MutableList<T>.mathList(_fun: (T) -> T): MutableList<T> {
                                for (i in this) {
                                    this.add(_fun(i))   <---   ERROR
                                }
                                return this   <--- ERROR
                            }
                            

                            决定

                            添加到新列表时效果很好:

                            fun <T> MutableList<T>.mathList(_fun: (T) -> T): MutableList<T> {
                                val newList = mutableListOf<T>()   <---   DECISION
                                for (i in this) {
                                    newList.add(_fun(i))   <---   DECISION
                                }
                                return newList   <---   DECISION
                            }
                            

                            【讨论】:

                              【解决方案23】:

                              只需在您的 ArrayList.remove(A) 语句后添加一个 break

                              【讨论】:

                              • 你能补充一些解释吗?
                              猜你喜欢
                              • 2013-08-29
                              • 2018-05-09
                              • 2020-03-18
                              • 2021-03-01
                              • 2017-02-04
                              • 2017-11-27
                              • 1970-01-01
                              相关资源
                              最近更新 更多