迭代集合,在循环中删除对象时避免使用ConcurrentModificationException

时间:2008-10-21 23:23:33

标签: java collections

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

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

ConcurrentModificationException等......这显然有时有效,但并非总是如此。这是一些特定的代码:

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

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

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

    System.out.println(l);
}

这当然会导致:

Exception in thread "main" java.util.ConcurrentModificationException

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

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

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

30 个答案:

答案 0 :(得分:1541)

Iterator.remove()是安全的,您可以像这样使用它:

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

// This is a clever way to create the iterator and call iterator.hasNext() like
// you would do in a while-loop. It would be the same as doing:
//     Iterator<String> iterator = list.iterator();
//     while (iterator.hasNext()) {
for (Iterator<String> iterator = list.iterator(); iterator.hasNext();) {
    String string = iterator.next();
    if (string.isEmpty()) {
        // Remove the current element from the iterator and the list.
        iterator.remove();
    }
}

请注意,Iterator.remove()是在迭代期间修改集合的唯一安全方法;如果在迭代过程中基础集合以任何其他方式修改 ,则行为未指定。

来源: docs.oracle > The Collection Interface


同样,如果你有一个ListIterator并希望添加项,则可以使用ListIterator#add,原因与使用Iterator#remove相同 - 它旨在允许它。


在您的情况下,您尝试从列表中删除,但如果在迭代其内容时尝试putMap,则适用相同的限制。

答案 1 :(得分:331)

这有效:

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

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

答案 2 :(得分:183)

使用Java 8,您可以使用the new removeIf method。适用于您的示例:

Collection<Integer> coll = new ArrayList<>();
//populate

coll.removeIf(i -> i == 5);

答案 3 :(得分:40)

由于问题已经得到解答,即最好的方法是使用迭代器对象的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

答案 4 :(得分:25)

您可以像上面提到的那样直接使用迭代器,或者保留第二个集合并将要删除的每个项目添加到新集合中,然后在最后删除所有项目。这允许你继续使用for-each循环的类型安全性,代价是增加内存使用和cpu时间(除非你有真正的大型列表或真正的旧计算机,否则不应该是一个大问题)

public static void main(String[] args)
{
    Collection<Integer> l = new ArrayList<Integer>();
    Collection<Integer> itemsToRemove = 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)
            itemsToRemove.add(i);
    }

    l.removeAll(itemsToRemove);
    System.out.println(l);
}

答案 5 :(得分:17)

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

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

那就是说,我对你在Java 8中有更好的方法感到高兴,例如流上的removeIffilter

答案 6 :(得分:16)

Claudius相同的答案,带有for循环:

for (Iterator<Object> it = objects.iterator(); it.hasNext();) {
    Object object = it.next();
    if (test) {
        it.remove();
    }
}

答案 7 :(得分:11)

使用Eclipse Collections(以前称为GS Collections),MutableCollection上定义的方法removeIf将有效:

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的removeIf接口上添加了默认的java.util.Collection方法。

注意:我是Eclipse Collections的提交者。

答案 8 :(得分:9)

制作现有列表的副本并迭代新副本。

for (String str : new ArrayList<String>(listOfStr))     
{
    listOfStr.remove(/* object reference or index */);
}

答案 9 :(得分:7)

人们声称一个不能从正在循环的循环中删除的集合中删除。我只是想指出技术上是不正确的并准确描述(我知道OP的问题是如此先进以至于不知道这一点)这个假设背后的代码:

Colletion

并不是你不能从迭代的break中删除,而是你不能继续迭代。因此,上面代码中的document.getElementByName("myname").innerHTML = 'Whats';

道歉,如果这个答案是一个有点专业的用例,更适合我从这里来的原始thread,那个被标记为重复(尽管这个线程显得更加细致)并锁定。

答案 10 :(得分:7)

使用传统的for循环

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

答案 11 :(得分:2)

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

答案 12 :(得分:1)

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

//"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;
}

这样可以避免并发异常。

答案 13 :(得分:1)

您可以使用 while 循环。

Iterator<Map.Entry<String, String>> iterator = map.entrySet().iterator();
while(iterator.hasNext()){
    Map.Entry<String, String> entry = iterator.next();
    if(entry.getKey().equals("test")) {
        iterator.remove();
    } 
}

答案 14 :(得分:1)

  

最好的方法(推荐)是使用java.util.Concurrent包。通过   使用此包可以轻松避免此异常。参考   修改后的代码

select  (select ConfigItemDescripcion from SGRC_ConfigItem where ConfigId = 'SEGM' and ConfigItemId = SegmentoId) Segmento,
             (select ConfigItemDescripcion from SGRC_ConfigItem where ConfigId = 'MRCA' and ConfigItemId = MarcaId) Marca,
             Producto, 
             Familia
from sgrc_emisor 
where EmisorCuenta = '3702406435'

答案 15 :(得分:1)

ConcurrentHashMapConcurrentLinkedQueueConcurrentSkipListMap可能是另一种选择,因为即使您删除或添加项目,它们也不会抛出任何ConcurrentModificationException。

答案 16 :(得分:1)

另一种方法是创建arrayList的副本:

List<Object> l = ...

List<Object> iterationList = ImmutableList.copyOf(l);

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

}

答案 17 :(得分:0)

我知道这个问题仅假设Collection,而不是具体假设任何List。但是对于确实使用List引用的那些阅读此问题的人,可以通过ConcurrentModificationException循环(在其中进行修改)避免使用while循环,如果您想避免使用Iterator (或者是要总体上避免使用它,还是专门避免使用它来实现不同于在每个元素上从头到尾停止的循环顺序(我相信这是唯一的顺序{ {1}}本身可以做到)]):

*更新:请参见下面的注释,以澄清类似情况也可以通过传统 -for循环实现。

Iterator

该代码没有ConcurrentModificationException。

我们看到循环不是从开始就开始,也不是在每个元素处停止(我相信final List<Integer> list = new ArrayList<>(); for(int i = 0; i < 10; ++i){ list.add(i); } int i = 1; while(i < list.size()){ if(list.get(i) % 2 == 0){ list.remove(i++); } else { i += 2; } } 本身无法做到)。

FWIW我们还看到Iteratorget调用,如果它的引用仅仅是list(而不是更具体的Collection类型的引用),则无法完成此操作List)-Collection接口包含List,但get接口不包含。如果不是因为这种差异,则Collection引用可以改为list [因此,从技术上讲,此答案将是直接答案,而不是切向答案]。

FWIWW相同的代码经过修改后可以在每个元素的起始处开始停止(就像Collection顺序一样):

Iterator

答案 18 :(得分:0)

我最终得到了这个 ConcurrentModificationException,同时使用 stream().map() 方法迭代列表。但是,for(:) 在迭代和修改列表时没有抛出异常。

这是代码片段,如果对任何人有帮助: 在这里,我正在迭代 ArrayList<BuildEntity> ,并使用 list.remove(obj)

修改它
 for(BuildEntity build : uniqueBuildEntities){
            if(build!=null){
                if(isBuildCrashedWithErrors(build)){
                    log.info("The following build crashed with errors ,  will not be persisted -> \n{}"
                            ,build.getBuildUrl());
                    uniqueBuildEntities.remove(build);
                    if (uniqueBuildEntities.isEmpty()) return  EMPTY_LIST;
                }
            }
        }
        if(uniqueBuildEntities.size()>0) {
            dbEntries.addAll(uniqueBuildEntities);
        }

答案 19 :(得分:0)

并发修改异常

  1. 单线程
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
    String value = iter.next()
    if (value == "A") {
        //throws ConcurrentModificationException
        list.remove(it.next());
    }
}

解决方案:迭代器remove()方法

Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
    String value = iter.next()
    if (value == "A") {
        it.remove()
    }
}
  1. 多线程
  • 复制/转换并迭代另一个集合。对于小型收藏
  • synchronize[About]
  • 线程安全集合[About]

答案 20 :(得分:0)

现在,您可以使用以下代码删除

l.removeIf(current -> current == 5);

答案 21 :(得分:0)

您还可以使用递归

Java中的递归是一个过程,其中方法连续不断地调用自身。 java中的一种调用自身的方法称为递归方法。

答案 22 :(得分:0)

尝试此操作(删除列表中等于i的所有元素):

for (Object i : l) {
    if (condition(i)) {
        l = (l.stream().filter((a) -> a != i)).collect(Collectors.toList());
    }
}

答案 23 :(得分:0)

一种解决方案是旋转列表并删除第一个元素,以避免ConcurrentModificationException或IndexOutOfBoundsException

int n = list.size();
for(int j=0;j<n;j++){
    //you can also put a condition before remove
    list.remove(0);
    Collections.rotate(list, 1);
}
Collections.rotate(list, -1);

答案 24 :(得分:0)

我知道这个问题对于Java 8来说已经太老了,但是对于那些使用Java 8的用户,您可以轻松地使用removeIf():

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

l.removeIf(i -> i.intValue() == 5);

答案 25 :(得分:0)

修改线程安全集合的示例:

public class Example {
    private final List<String> queue = Collections.synchronizedList(new ArrayList<String>());

    public void removeFromQueue() {
        synchronized (queue) {
            Iterator<String> iterator = queue.iterator();
            String string = iterator.next();
            if (string.isEmpty()) {
                iterator.remove();
            }
        }
    }
}

答案 26 :(得分:0)

如果 ArrayList:remove(int index) - if(index是最后一个元素的位置),它会在没有System.arraycopy()的情况下避免,并且没有时间用于此。

如果(索引减少),

arraycopy time会增加,顺便说一下list的元素也会减少!

最有效的删除方式是 - 按降序删除其元素: while(list.size()>0)list.remove(list.size()-1); //需要O(1) while(list.size()>0)list.remove(0); //需要O(factorial(n))

//region prepare data
ArrayList<Integer> ints = new ArrayList<Integer>();
ArrayList<Integer> toRemove = new ArrayList<Integer>();
Random rdm = new Random();
long millis;
for (int i = 0; i < 100000; i++) {
    Integer integer = rdm.nextInt();
    ints.add(integer);
}
ArrayList<Integer> intsForIndex = new ArrayList<Integer>(ints);
ArrayList<Integer> intsDescIndex = new ArrayList<Integer>(ints);
ArrayList<Integer> intsIterator = new ArrayList<Integer>(ints);
//endregion

// region for index
millis = System.currentTimeMillis();
for (int i = 0; i < intsForIndex.size(); i++) 
   if (intsForIndex.get(i) % 2 == 0) intsForIndex.remove(i--);
System.out.println(System.currentTimeMillis() - millis);
// endregion

// region for index desc
millis = System.currentTimeMillis();
for (int i = intsDescIndex.size() - 1; i >= 0; i--) 
   if (intsDescIndex.get(i) % 2 == 0) intsDescIndex.remove(i);
System.out.println(System.currentTimeMillis() - millis);
//endregion

// region iterator
millis = System.currentTimeMillis();
for (Iterator<Integer> iterator = intsIterator.iterator(); iterator.hasNext(); )
    if (iterator.next() % 2 == 0) iterator.remove();
System.out.println(System.currentTimeMillis() - millis);
//endregion
  • 索引循环:1090毫秒
  • for desc index: 519 msec ---最好的
  • for iterator:1043毫秒

答案 27 :(得分:0)

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

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

干杯!

答案 28 :(得分:-2)

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

  

“创建第二个空数组并仅添加您想要保留的数组”

我不记得我从哪里读到这个...因为我将使这个wiki成为希望有人找到它或者只是为了不赚取代表我不配。 < / p>

答案 29 :(得分:-3)

Collection<Integer> l = new ArrayList<Integer>();//Do the collection thing...

l.removeIf(i -> i == 5);      //iterates through the collection and removes every occurence of 5

Handy中引入了Jdk 8中的Lambda表达式和Collection方法,并添加了一些语法糖。

方法removeIf遍历集合并使用谓词进行过滤。谓词是返回布尔值的参数的函数。 就像boolean _bool = (str) -> str.equals("text");