[java] 迭代集合,避免在循环中移除时出现ConcurrentModificationException



Answers

傻我:

Iterator<Integer> iter = l.iterator();
while (iter.hasNext()) {
    if (iter.next().intValue() == 5) {
        iter.remove();
    }
}

我认为,因为foreach循环是用于迭代的语法糖,所以使用迭代器将无济于事......但它会为您提供.remove()功能。

Question

我们都知道你不能这样做:

for (Object i : l) {
    if (condition(i)) {
        l.remove(i);
    }
}

ConcurrentModificationException等...这显然有效,但并不总是。 以下是一些特定的代码:

public static void main(String[] args) {
    Collection<Integer> l = new ArrayList<Integer>();

    for (int i=0; i < 10; ++i) {
        l.add(new Integer(4));
        l.add(new Integer(5));
        l.add(new Integer(6));
    }

    for (Integer i : l) {
        if (i.intValue() == 5) {
            l.remove(i);
        }
    }

    System.out.println(l);
}

这当然会导致:

Exception in thread "main" java.util.ConcurrentModificationException

即使多线程没有这样做...无论如何。

这个问题最好的解决方案是什么? 如何在循环中从集合中删除项目而不抛出此异常?

我也在这里使用任意的Collection ,不一定是ArrayList ,所以你不能依赖get




使用Eclipse集合 (以前称为GS集合 ),在removeIf定义的方法MutableCollection可以工作:

MutableList<Integer> list = Lists.mutable.of(1, 2, 3, 4, 5);
list.removeIf(Predicates.lessThan(3));
Assert.assertEquals(Lists.mutable.of(3, 4, 5), list);

使用Java 8 Lambda语法可以编写如下代码:

MutableList<Integer> list = Lists.mutable.of(1, 2, 3, 4, 5);
list.removeIf(Predicates.cast(integer -> integer < 3));
Assert.assertEquals(Lists.mutable.of(3, 4, 5), list);

Predicates.cast()的调用在这里是必需的,因为在Java 8的java.util.Collection接口上添加了一个默认的removeIf方法。

注意:我是Eclipse集合的提交者。




在这种情况下,一个常见的技巧是(是?)倒退:

for(int i = l.size() - 1; i >= 0; i --) {
  if (l.get(i) == 5) {
    l.remove(i);
  }
}

也就是说,我非常高兴在Java 8中拥有更好的方法,例如removeIf或者在流上filter




我对上述问题有一个建议。 无需二级名单或任何额外的时间。 请找一个能够以不同方式做同样事情的例子。

//"list" is ArrayList<Object>
//"state" is some boolean variable, which when set to true, Object will be removed from the list
int index = 0;
while(index < list.size()) {
    Object r = list.get(index);
    if( state ) {
        list.remove(index);
        index = 0;
        continue;
    }
    index += 1;
}

这将避免并发异常。




您可以使用for-loop迭代列表,并且需要调用list.remove(0)。 您需要使用零硬编码索引remove index参数。 另请参阅此答案 :

List<Integer> list = new ArrayList<Integer>();

list.add(1);
list.add(2);
list.add(3);
list.add(4);
int list_size = list.size();
for (int i = 0; i < list_size; i++) {
    list.remove(0);
}



for (Integer i : l)
{
    if (i.intValue() == 5){
            itemsToRemove.add(i);
            break;
    }
}

如果跳过内部iterator.next()调用,则catch从列表中移除元素。 它仍然有效! 虽然我不打算编写这样的代码,但它有助于理解它背后的概念:-)

干杯!




用传统的循环

ArrayList<String> myArray = new ArrayList<>();

   for (int i = 0; i < myArray.size(); ) {
        String text = myArray.get(i);
        if (someCondition(text))
             myArray.remove(i);
        else 
             i++;
      }



由于这个问题已经得到解决,即最好的方法是使用迭代器对象的remove方法,我会进入发生错误"java.util.ConcurrentModificationException"的地方的细节。

每个集合类都有一个私有类,它实现了Iterator接口并提供诸如next()remove()hasNext()

接下来的代码看起来像这样...

public E next() {
    checkForComodification();
    try {
        E next = get(cursor);
        lastRet = cursor++;
        return next;
    } catch(IndexOutOfBoundsException e) {
        checkForComodification();
        throw new NoSuchElementException();
    }
}

这里的checkForComodification方法被实现为

final void checkForComodification() {
    if (modCount != expectedModCount)
        throw new ConcurrentModificationException();
}

所以,正如你所看到的,如果你明确地尝试从集合中移除一个元素。 它导致modCountexpectedModCount不同,导致ConcurrentModificationException异常。




ListIterator允许您添加或删除列表中的项目。 假设你有一个Car对象列表:

List<Car> cars = ArrayList<>();
// add cars here...

for (ListIterator<Car> carIterator = cars.listIterator();  carIterator.hasNext(); )
{
   if (<some-condition>)
   { 
      carIterator().remove()
   }
   else if (<some-other-condition>)
   { 
      carIterator().add(aNewCar);
   }
}



这可能不是最好的方式,但对于大多数小案例来说,这应该是可以接受的:

“创建第二个空阵列并只添加你想保留的阵列”

我不记得我从哪里阅读这篇文章...对于公平,我会让这个维基百科希望有人发现它,或者只是不要赢得代表我不配。




Links