如何在迭代时从"ArrayList"中删除元素时避免"ConcurrentModificationException"?

Ern*_*dis 333 java iterator list arraylist

我试图删除一些元素ArrayList迭代它像这样:

for (String str : myArrayList) {
    if (someCondition) {
        myArrayList.remove(str);
    }
}
Run Code Online (Sandbox Code Playgroud)

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

ars*_*jii 545

使用Iterator并致电remove():

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

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

    if (someCondition)
        iter.remove();
}
Run Code Online (Sandbox Code Playgroud)

  • 好笑,我在`String str = iter.next();`上遇到了同样的异常!带收藏的Java很糟糕! (35认同)
  • 谢谢,现在一切正常:)我认为这个答案是最好的,因为代码很容易阅读. (4认同)
  • @ErnestasGruodis权衡的是,它现在适用于该方法的其余部分. (4认同)
  • 使用这种方法也得到了同样的异常。 (3认同)
  • 如果我想删除当前迭代以外的东西(比如它在索引2上,但我需要同时删除索引7),该怎么办?每当我尝试.remove(index)时,它都会给我一个ConcurrentModificationException. (2认同)

Kev*_*lia 178

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

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

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

  • +1我喜欢这个无迭代的解决方案. (17认同)
  • @KevinDiTraglia是否有理由使用比您需要的更多资源?它不像迭代器很难处理或使代码混乱. (4认同)
  • @EricStein如果我们采取额外步骤并使用不可变列表(如Guava库中的那些),那么在处理多线程并发问题时这会变得更有吸引力. (3认同)
  • @EricStein我通常会在我想要添加到列表中的情况下结束,而额外的资源通常是微不足道的.它只是一种替代解决方案,两者都有其优点和缺点. (2认同)

Mik*_*sky 87

Java 8用户可以这样做: list.removeIf(...)

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

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

  • 如果他们还添加了`removeWhile`就好了 (2认同)

Eri*_*ein 61

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

for (final Iterator iterator = myArrayList.iterator(); iterator.hasNext(); ) {
    iterator.next();
    if (someCondition) {
        iterator.remove();
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 我觉得这个答案更好地沟通; 迭代器仅限于for循环,迭代的细节在for语句中.视觉噪音较小. (8认同)

Dim*_*huk 35

不不不!

在单线程任务中,您不需要使用Iterator,而且还需要使用CopyOnWriteArrayList(由于性能损失).

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

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

例如,当在修改后的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));
            }
        }
Run Code Online (Sandbox Code Playgroud)

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

    // 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
        }
    }
Run Code Online (Sandbox Code Playgroud)

因此,尝试使用索引方法迭代集合并避免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)
Run Code Online (Sandbox Code Playgroud)

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

  • 值得注意的是,给定LinkedList内部工作的方式,Iterator比后续的`get(i)`调用增加的`i`更高效. (18认同)
  • 你可以避免`i--; //通过向下循环避免跳过被移位的元素:`for(int i = testList.size() - 1; i> = 0; i--){...}`而且,而不是`testList.remove (testList.indexOf(currElement));`你可以简单地写`testList.remove(i);` (7认同)

Pra*_*ate 8

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

    //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);
Run Code Online (Sandbox Code Playgroud)

  • 是的,但是Java文档说"这通常成本太高,但是当遍历操作大大超过突变时可能比替代方法更有效,并且当您不能或不想同步遍历但需要排除并发之间的干扰时非常有用线程". (2认同)

Jun*_*san 7

如果要在遍历期间修改列表,则需要使用Iterator.然后您可以使用iterator.remove()在遍历期间删除元素.


Pra*_*ran 7

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();
     }
 }
Run Code Online (Sandbox Code Playgroud)

  • 在此答案中,您从列表中删除项目的位置?OP 询问如何在删除元素时避免“ConcurrentModificationException”。我看不出其他人对这个答案投赞成票的任何理由。 (2认同)

Car*_*ohn 7

另一种方法是将您转换Listarray,迭代它们并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());
}
Run Code Online (Sandbox Code Playgroud)

  • 该解决方案仅适用于小规模的列表。想象一下包含数千个项目的列表,转换为数组将非常昂贵。 (2认同)