【问题标题】:How to avoid "ConcurrentModificationException" while removing elements from `ArrayList` while iterating it? [duplicate]如何在迭代时从 `ArrayList` 中删除元素时避免 \"ConcurrentModificationException\"? [复制]
【发布时间】:2018-05-09 12:19:12
【问题描述】:

我正在尝试从 ArrayList 中删除一些元素,同时像这样迭代它:

for (String str : myArrayList) {
    if (someCondition) {
        myArrayList.remove(str);
    }
}

当然,在迭代 myArrayList 的同时尝试从列表中删除项目时,我得到了 ConcurrentModificationException。有没有一些简单的解决方案来解决这个问题?

【问题讨论】:

  • 我使用克隆对象来解决这个问题。

标签: java list arraylist iterator


【解决方案1】:

使用 Iterator 并调用 remove()

Iterator<String> iter = myArrayList.iterator();

while (iter.hasNext()) {
    String str = iter.next();

    if (someCondition)
        iter.remove();
}

【讨论】:

  • 谢谢,现在一切正常 :) 我认为这个答案是最好的,因为代码很容易阅读。
  • @ErnestasGruodis 权衡是 iter 现在在该方法的其余部分的范围内。
  • 如果我想删除当前迭代以外的内容怎么办(假设它在索引 2 上,但我需要同时删除索引 7)。每当我尝试通过 .remove(index) 时,它都会给我一个 ConcurrentModificationException。
  • 有趣的是,我在String str = iter.next(); 上遇到了同样的异常!带有集合的 Java 很烂!
  • 使用这种方法也有同样的异常。
【解决方案2】:

作为其他人的答案的替代方案,我总是这样做:

List<String> toRemove = new ArrayList<String>();
for (String str : myArrayList) {
    if (someCondition) {
        toRemove.add(str);
    }
}
myArrayList.removeAll(toRemove);

这将避免您必须直接处理迭代器,但需要另一个列表。无论出于何种原因,我一直更喜欢这条路线。

【讨论】:

  • +1 我喜欢这个无迭代器的解决方案。
  • @KevinDiTraglia 是否有理由使用比您需要的更多的资源?它不像迭代器那样难以使用或使代码混乱。
  • @EricStein 我通常会遇到我也想添加到列表中的情况,而额外的资源大多是微不足道的。这只是一种替代解决方案,两者各有利弊。
  • @KevinDiTraglia 我同意资源通常可以忽略不计。
  • @EricStein 如果我们采取额外的步骤并使用不可变列表(如 Guava 库中的列表),那么在处理多线程并发问题时这会变得更有吸引力。
【解决方案3】:

Java 8 用户可以做到这一点:list.removeIf(...)

    List<String> list = new ArrayList<>(Arrays.asList("a", "b", "c"));
    list.removeIf(e -> (someCondition));

它将删除列表中满足 someCondition 的元素

【讨论】:

  • 是的,如果你可以使用 Java 8,那就更好了。
  • 如果他们也添加了 removeWhile 就好了
  • @damluar 我不明白为什么removeWhileremoveIf 会删除所有符合条件的元素。
  • 但是,如果您只想删除第一个元素直到/当条件满足时怎么办?
  • 他们在 JDK 9 中添加了类似 removeWhile 的内容。
【解决方案4】:

您必须使用迭代器的 remove() 方法,这意味着没有增强的 for 循环:

for (final Iterator iterator = myArrayList.iterator(); iterator.hasNext(); ) {
    iterator.next();
    if (someCondition) {
        iterator.remove();
    }
}

【讨论】:

  • 我觉得这个答案传达得更好;迭代器被限制在 for 循环中,迭代的细节在 for 语句中。更少的视觉噪音。
  • 如果您将类型参数添加到 Iterator 并将 iterator.next() 分配给某个变量,那么如果这是最佳解决方案,您实际上可以用它做一些事情
  • 为什么将迭代器声明为 final ?
  • @kh.tab 我认为将所有不打算重新分配的变量声明为 final 是一个好习惯。我只希望“最终”是默认值。
【解决方案5】:

不不不!

在单个威胁任务中,您不需要使用 Iterator,此外,CopyOnWriteArrayList(由于性能下降)。

解决方案要简单得多:尝试使用规范的 for 循环而不是 for-each 循环.

根据 Java 版权所有者(几年前是 Sun,现在是 Oracle)for-each loop guide 的说法,它使用迭代器遍历集合并隐藏它以使代码看起来更好。但是,不幸的是,正如我们所看到的,它产生的问题多于利润,否则就不会出现这个话题。

例如,在修改后的 ArrayList 上进入下一次迭代时,这段代码将导致 java.util.ConcurrentModificationException:

        // process collection
        for (SomeClass currElement: testList) {

            SomeClass founDuplicate = findDuplicates(currElement);
            if (founDuplicate != null) {
                uniqueTestList.add(founDuplicate);
                testList.remove(testList.indexOf(currElement));
            }
        }

但是下面的代码工作得很好:

    // process collection
    for (int i = 0; i < testList.size(); i++) {
        SomeClass currElement = testList.get(i);

        SomeClass founDuplicate = findDuplicates(currElement);
        if (founDuplicate != null) {
            uniqueTestList.add(founDuplicate);
            testList.remove(testList.indexOf(currElement));
            i--; //to avoid skipping of shifted element
        }
    }

因此,尝试使用索引方法迭代集合并避免 for-each 循环,因为它们不等价! For-each 循环使用一些内部迭代器,这些迭代器检查集合修改并抛出 ConcurrentModificationException 异常。要确认这一点,请仔细查看使用我发布的第一个示例时打印的堆栈跟踪:

Exception in thread "main" java.util.ConcurrentModificationException
    at java.util.AbstractList$Itr.checkForComodification(AbstractList.java:372)
    at java.util.AbstractList$Itr.next(AbstractList.java:343)
    at TestFail.main(TestFail.java:43)

对于多线程,使用相应的多任务方法(如 synchronized 关键字)。

【讨论】:

  • 值得注意的是,鉴于 LinkedList 的内部工作方式,迭代器是远的比使用递增的i 的后续get(i) 调用更有效。
  • 很好的评论和细节,谢谢
  • 同意安加德的观点。人们通常只能访问泛型 List 类型,而所使用的实现是未知的。如果使用的实现是 LinkedList,则使用 C 风格的 for 循环遍历 List 以检索每个列表将导致 O(n2) 复杂度。
  • 您可以通过向下循环来避免i--; //to avoid skipping of shifted elementfor (int i = testList.size()-1; i &gt;= 0; i--) { ... }此外,您可以简单地写testList.remove(i);而不是testList.remove(testList.indexOf(currElement));
  • @Angad 但是使用迭代器会导致提到异常,因为它依赖于上一个-当前-下一个关系,如果从集合中删除元素,该关系就会被破坏。这里我们应该按性能命中付费。
【解决方案6】:

虽然其他建议的解决方案有效,但如果您真的希望该解决方案成为线程安全的,您应该将 ArrayList 替换为 CopyOnWriteArrayList

    //List<String> s = new ArrayList<>(); //Will throw exception
    List<String> s = new CopyOnWriteArrayList<>();
    s.add("B");
    Iterator<String> it = s.iterator();
    s.add("A");

    //Below removes only "B" from List
    while (it.hasNext()) {
        s.remove(it.next());
    }
    System.out.println(s);

【讨论】:

  • 是的,但是 Java 文档说“这通常成本太高,但当遍历操作远远超过突变时,它可能比替代方法更有效,并且当您不能或不想同步遍历但需要排除并发之间的干扰时很有用线程。”
【解决方案7】:

如果你想在遍历期间修改你的List,那么你需要使用Iterator。然后你可以使用 iterator.remove() 在遍历过程中删除元素。

【讨论】:

    【解决方案8】:
    List myArrayList  = Collections.synchronizedList(new ArrayList());
    
    //add your elements  
     myArrayList.add();
     myArrayList.add();
     myArrayList.add();
    
    synchronized(myArrayList) {
        Iterator i = myArrayList.iterator(); 
         while (i.hasNext()){
             Object  object = i.next();
         }
     }
    

    【讨论】:

    • 在这个答案中,你在哪里从列表中删除项目? OP 问如何在删除元素时避免“ConcurrentModificationException”。我看不出其他人赞成这个答案的任何理由。
    【解决方案9】:

    一种替代方法是将您的 List 转换为 array,迭代它们并根据您的逻辑直接从 List 中删除它们。

    List<String> myList = new ArrayList<String>(); // You can use either list or set
    
    myList.add("abc");
    myList.add("abcd");
    myList.add("abcde");
    myList.add("abcdef");
    myList.add("abcdefg");
    
    Object[] obj = myList.toArray();
    
    for(Object o:obj)  {
        if(condition)
            myList.remove(o.toString());
    }
    

    【讨论】:

    • 为什么在删除时有一个 object.toString()?不应该只是'o'吗?
    • @TheMorfeus 可以只是'o'。但是我使用 toString() 方法来避免 IDE 中的“可疑方法调用”错误。无其他具体原因。
    • 此解决方案仅适用于小列表。试想一个包含数千个项目的列表,转换为数组将非常昂贵。
    【解决方案10】:

    您可以使用迭代器 remove() 函数从基础集合对象中删除对象。但在这种情况下,您可以从列表中删除同一个对象而不是任何其他对象。

    来自here

    【讨论】:

    • 链接属于评论部分,除非它们支持您的帖子。您应该编辑您的答案以包含解释,然后将链接作为参考。
    • 这个确实有效并解决了问题!谢谢!
    猜你喜欢
    • 2013-08-29
    • 2011-12-27
    • 2017-11-27
    • 2016-07-15
    • 2014-06-03
    • 2014-02-10
    • 2011-07-04
    相关资源
    最近更新 更多