【问题标题】:How to avoid "ConcurrentModificationException" while removing elements from `ArrayList` while iterating it? [duplicate]如何在迭代时从`ArrayList`中删除元素时避免“ConcurrentModificationException”? [复制]
【发布时间】:2013-08-29 05:03:50
【问题描述】:

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

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

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

【问题讨论】:

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

标签: java list arraylist iterator


【解决方案1】:

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

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

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

【讨论】:

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

你必须使用迭代器的 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 是一个好习惯。我只希望“最终”是默认值。
【解决方案3】:

另一种方法是将您的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 的“可疑方法调用”错误。没有其他具体原因。
  • 此解决方案仅适用于小尺寸列表。试想一个包含数千个项目的列表,转换为数组将非常昂贵。
【解决方案4】:

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 的内容。
【解决方案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)

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

【讨论】:

  • 值得注意的是,鉴于 LinkedList 在内部的工作方式,迭代器比后续的 get(i) 调用更高效,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(testList.indexOf(currElement));而不是testList.remove(testList.indexOf(currElement));
  • @Angad 但是使用迭代器会导致提到的异常,因为它依赖于前-当前-下一个关系,如果从集合中删除一个元素,这种关系就会被破坏。在这里,我们应该按性能命中付费。
【解决方案6】:

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

来自here

【讨论】:

  • 链接属于评论部分,除非它们支持您的帖子。您应该编辑您的答案以包含解释,然后将链接作为参考。
  • 这个确实有效,解决了问题!谢谢!
【解决方案7】:

虽然其他建议的解决方案有效,但如果您真的希望解决方案成为线程安全的,您应该将 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 文档说“这通常成本太高,但是当遍历操作的数量远远超过突变时,它可能比替代方法更有效,并且在您不能或不想同步遍历时很有用,但需要排除并发线程之间的干扰。”
【解决方案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】:

使用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 糟透了!
  • 使用这种方法也遇到了同样的异常。
【解决方案10】:

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

【讨论】:

    猜你喜欢
    • 2018-05-09
    • 2011-12-27
    • 2017-11-27
    • 2016-07-15
    • 2014-06-03
    • 2014-02-10
    • 2011-07-04
    相关资源
    最近更新 更多