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

185 java foreach arraylist

我有一个我想迭代的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);
    }
}
Run Code Online (Sandbox Code Playgroud)

a.doSomething可能打电话Test.removeA();

Jon*_*eet 289

两种选择:

  • 创建要删除的值列表,在循环中添加该列表,然后originalList.removeAll(valuesToRemove)在结尾处调用
  • remove()在迭代器本身上使用该方法.请注意,这意味着您无法使用增强型for循环.

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

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

  • 我应该提到我删除了代码另一部分中的元素,而不是循环本身。 (2认同)
  • 此解决方案也会导致 java.util.ConcurrentModificationException,请参阅 http://stackoverflow.com/a/18448699/2914140。 (2认同)

Var*_*har 16

来自ArrayList的JavaDocs

此类的iterator和listIterator方法返回的迭代器是快速失败的:如果在创建迭代器之后的任何时候对列表进行结构修改,除了通过迭代器自己的remove或add方法之外,迭代器将抛出ConcurrentModificationException.

  • 就像它说的,*除非通过迭代器自己的删除或添加方法* (3认同)
  • 问题的答案在哪里? (2认同)

suh*_*n07 10

您试图从高级"for循环"中的列表中删除值,这是不可能的,即使您应用任何技巧(您在代码中执行).更好的方法是将迭代器级别编码为其他建议.

我想知道人们怎么没有建议传统的for循环方法.

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

这也有效.

  • 这不正确!!!当您删除一个元素时,下一个元素将占据其位置,而 i 增加时,下一个元素不会在下一次迭代中检查。在这种情况下,你应该去 for( int i = lStringList.size(); i&gt;-1; i-- ) (3认同)
  • 同意!另一种方法是执行 i--;在 for 循环内的 if 条件中。 (2认同)

小智 10

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

yourList.removeIf((A a) -> a.value == 2);
Run Code Online (Sandbox Code Playgroud)

更多信息可以在这里找到


小智 8

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

每次从列表中删除元素时,后面的元素都将向前推进.只要您不更改迭代之外的元素,以下代码就可以工作.

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


小智 7

您还可以使用CopyOnWriteArrayList来代替ArrayList. 这是从 JDK 1.5 开始最新推荐的方法。


小智 6

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

所以尝试:

for(int i = 0; i < list.size(); i++){
    lista.get(i).action();
}
Run Code Online (Sandbox Code Playgroud)


小智 6

在迭代列表时,如果要删除元素是可能的。让我们看下面的例子,

ArrayList<String>  names = new ArrayList<String>();
        names.add("abc");
        names.add("def");
        names.add("ghi");
        names.add("xyz");
Run Code Online (Sandbox Code Playgroud)

我有以上名称的数组列表。我想从上面的列表中删除“ def”名称,

for(String name : names){
    if(name.equals("def")){
        names.remove("def");
    }
}
Run Code Online (Sandbox Code Playgroud)

上面的代码引发ConcurrentModificationException异常,因为您在迭代时正在修改列表。

因此,要通过这种方式从Arraylist中删除“ def”名称,

Iterator<String> itr = names.iterator();            
while(itr.hasNext()){
    String name = itr.next();
    if(name.equals("def")){
        itr.remove();
    }
}
Run Code Online (Sandbox Code Playgroud)

上面的代码,通过迭代器,我们可以从Arraylist中删除“ def”名称,然后尝试打印该数组,您将看到以下输出。

输出:[abc,ghi,xyz]


Bha*_*kar 5

一种选择是将removeA方法修改为-

public void removeA(A a,Iterator<A> iterator) {
     iterator.remove(a);
     }
Run Code Online (Sandbox Code Playgroud)

但这意味着你doSomething()应该能够传递iteratorremove方法.不是一个好主意.

你能用两步法做到这一点:在迭代列表的第一个循环中,不是删除所选元素,而是它们标记要删除.为此,您可以简单地将这些元素(浅拷贝)复制到另一个元素中List.

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

  • 我没有看到 Iterator 有 remove(a) 方法。remove() 没有参数 https://docs.oracle.com/javase/8/docs/api/java/util/Iterator.html 我错过了什么? (2认同)

ser*_*rup 5

这是一个示例,我使用不同的列表添加要删除的对象,然后我使用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));
}
Run Code Online (Sandbox Code Playgroud)


小智 5

有时老派是最好的。只需进行一个简单的 for 循环,但确保从列表末尾开始,否则当您删除项目时,您将与索引不同步。

List<String> list = new ArrayList<>();
for (int i = list.size() - 1; i >= 0; i--) {
  if ("removeMe".equals(list.get(i))) {
    list.remove(i);
  }
}
Run Code Online (Sandbox Code Playgroud)