如何在迭代和从ArrayList中移除元素时避免java.util.ConcurrentModificationException

我有一个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.do可能会调用Test.removeA();

两个选项:

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

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

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

从ArrayList的JavaDocs

这个类的迭代器和listIterator方法返回的迭代器是快速失败的:如果在迭代器被创build之后,在任何时候结构上修改了列表, 除了通过迭代器自己的remove或add方法以外 ,迭代器将抛出一个ConcurrentModificationExceptionexception。

一个选项是修改removeA方法到这个 –

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

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

你可以这样做两步法:在第一个循环中,当你遍历列表,而不是删除选定的元素, 标记被删除 。 为此,您可以简单地将这些元素(浅拷贝)复制到另一个List

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

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

每当你从列表中删除一个元素,之后的元素将被推进。 只要你不改变迭代以外的元素,下面的代码应该可以工作。

 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); } } 

这里是一个例子,我使用不同的列表来添加删除对象,然后使用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)); } 

您正试图从高级“for循环”中删除列表中的值,这是不可能的,即使您应用了任何技巧(您在代码中执行的操作)。 更好的方法是在这里build议其他代码迭代器级别。

我不知道人们如何不build议传统的循环方法。

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

这也适用。

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

所以试试:

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

做这样简单的事情:

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

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

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

我的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(); } 

在Java 8中,您可以使用Collection接口并通过调用removeIf方法来执行此操作:

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

更多信息可以在这里find