如何在迭代和从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