1268

我们都知道您不能执行以下操作,因为ConcurrentModificationException

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

但这显然有时有效,但并非总是如此。下面是一些具体的代码:

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.

4

30 回答 30

1654

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 > 集合接口


同样,如果您有 aListIterator并且想要添加项目,您可以使用ListIterator#add,出于同样的原因,您可以使用Iterator#remove - 它旨在允许它。


在您的情况下,您尝试从列表中删除,但如果尝试在put一段Map时间内迭代其内容,则同样的限制适用。

于 2008-10-21T23:27:15.203 回答
353

这有效:

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

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

于 2008-10-21T23:26:31.383 回答
233

在 Java 8 中,您可以使用removeIf方法。应用于您的示例:

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

coll.removeIf(i -> i == 5);
于 2014-05-28T10:11:44.693 回答
44

由于问题已经得到解答,即最好的方法是使用迭代器对象的 remove 方法,我将详细介绍"java.util.ConcurrentModificationException"引发错误的地方。

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

next 的代码看起来像这样......

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

因此,如您所见,如果您明确尝试从集合中删除一个元素。它导致modCount得到不同expectedModCount,导致异常ConcurrentModificationException

于 2010-05-15T19:57:56.713 回答
28

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

public static void main(String[] args)
{
    Collection<Integer> l = new ArrayList<Integer>();
    Collection<Integer> itemsToRemove = new ArrayList<>();
    for (int i=0; i < 10; i++) {
        l.add(Integer.of(4));
        l.add(Integer.of(5));
        l.add(Integer.of(6));
    }
    for (Integer i : l)
    {
        if (i.intValue() == 5) {
            itemsToRemove.add(i);
        }
    }

    l.removeAll(itemsToRemove);
    System.out.println(l);
}
于 2008-10-21T23:32:17.427 回答
19

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

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

也就是说,我很高兴您在 Java 8 中有更好的方法,例如removeIffilter流上。

于 2014-08-29T09:56:15.023 回答
17

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

for (Iterator<Object> it = objects.iterator(); it.hasNext();) {
    Object object = it.next();
    if (test) {
        it.remove();
    }
}
于 2013-08-21T12:39:45.337 回答
12

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

for (String str : new ArrayList<String>(listOfStr))     
{
    listOfStr.remove(/* object reference or index */);
}
于 2012-06-26T05:28:35.773 回答
12

使用Eclipse CollectionsMutableCollectionremoveIf上定义的方法将起作用:

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

此处调用 to是必要的,因为在 Java 8 的接口上添加Predicates.cast()了默认removeIf方法。java.util.Collection

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

于 2012-12-18T23:08:46.757 回答
10

使用传统的 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++;   
}
于 2017-04-16T20:29:06.973 回答
10

人们断言无法从 foreach 循环迭代的 Collection 中删除。我只是想指出这在技术上是不正确的并准确地描述了(我知道 OP 的问题是如此先进以至于避免知道这一点)该假设背后的代码:

for (TouchableObj obj : untouchedSet) {  // <--- This is where ConcurrentModificationException strikes
    if (obj.isTouched()) {
        untouchedSet.remove(obj);
        touchedSt.add(obj);
        break;  // this is key to avoiding returning to the foreach
    }
}

并不是说您不能从迭代中删除,Colletion而是一旦这样做就无法继续迭代。因此break在上面的代码中。

抱歉,如果这个答案是一个有点专业的用例并且更适合我从这里到达的原始线程,那个被标记为重复(尽管这个线程看起来更细微)并被锁定。

于 2018-03-17T11:02:30.790 回答
4

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

于 2016-06-23T11:18:58.893 回答
3

另一种方法是使用 arrayList 的副本仅用于迭代:

List<Object> l = ...
    
List<Object> iterationList = ImmutableList.copyOf(l);
    
for (Object curr : iterationList) {
    if (condition(curr)) {
        l.remove(curr);
    }
}
于 2019-03-14T11:18:16.507 回答
2

AListIterator允许您添加或删除列表中的项目。假设您有一个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);
   }
}
于 2017-10-13T15:16:20.077 回答
2

Java 并发修改异常

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

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

Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
    String value = iter.next()
    if (value == "A") {
        it.remove()
    }
}
  1. 多线程
  • 复制/转换并迭代另一个集合。对于小型收藏
  • synchronize[关于]
  • 线程安全集合[关于]
于 2021-03-31T14:01:16.110 回答
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;
}

这将避免并发异常。

于 2013-11-19T09:18:45.460 回答
1

最好的方法(推荐)是使用 java.util.Concurrent 包。通过使用这个包,你可以很容易地避免这个 Exception 。参考修改后的代码

public static void main(String[] args) {
    Collection<Integer> l = new CopyOnWriteArrayList<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);
}
于 2018-05-03T17:59:35.887 回答
1

我知道这个问题对于 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);
于 2018-09-26T20:15:21.577 回答
1

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

l.removeIf(current -> current == 5);
于 2020-09-22T14:22:27.867 回答
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();
    } 
}
于 2020-12-28T10:40:17.500 回答
0

如果ArrayList:remove(int index) - if(index is last element's position) 它可以避免System.arraycopy()并且不需要时间。

如果(索引减少),数组复制时间会增加,顺便说一下列表的元素也会减少!

最有效的删除方式是 - 按降序删除其元素: while(list.size()>0)list.remove(list.size()-1);//takes O(1) while(list.size()>0)list.remove(0);//takes 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 毫秒
  • 对于 desc 索引:519毫秒---最好的
  • 迭代器:1043 毫秒
于 2016-02-04T17:25:28.073 回答
0
for (Integer i : l)
{
    if (i.intValue() == 5){
            itemsToRemove.add(i);
            break;
    }
}

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

干杯!

于 2016-06-30T07:24:49.550 回答
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();
            }
        }
    }
}
于 2018-09-17T09:03:02.437 回答
0

我知道这个问题只是假设 a Collection,而不是更具体地说是 any List。但是对于那些确实在使用List参考文献的阅读这个问题的人,您可以避免ConcurrentModificationException使用while-loop(在其中进行修改时),而不是如果您想避免Iterator(或者如果您想总体上避免它,或者专门避免它以实现与在每个元素处停止的从头到尾不同的循环顺序[我相信这是Iterator它本身可以做的唯一顺序]):

*更新:请参阅下面的评论,阐明类似的情况也可以通过传统的-for-loop 实现。

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

该代码没有 ConcurrentModificationException。

在那里我们看到循环不是从一开始就开始,也不是在每个元素处都停止(我相信Iterator它自己做不到)。

FWIW 我们还看到get被调用 on list,如果它的引用只是Collection(而不是更具体List的 -type Collection)- Listinterface 包括get,但Collectioninterface 没有,则无法完成。如果不是因为这种差异,那么list参考可以改为Collection[因此从技术上讲,此答案将是直接答案,而不是切线答案]。

FWIWW 相同的代码在修改为在每个元素的开始处停止后仍然有效(就像Iterator顺序一样):

final List<Integer> list = new ArrayList<>();
for(int i = 0; i < 10; ++i){
    list.add(i);
}

int i = 0;
while(i < list.size()){
    if(list.get(i) % 2 == 0){
        list.remove(i);

    } else {
        ++i;
    }
}
于 2018-12-19T23:40:53.403 回答
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);
于 2019-07-31T20:16:13.733 回答
0

试试这个(删除列表中等于的所有元素i):

for (Object i : l) {
    if (condition(i)) {
        l = (l.stream().filter((a) -> a != i)).collect(Collectors.toList());
    }
}
于 2019-11-13T19:15:38.360 回答
0

我结束了这个ConcurrentModificationException,同时使用方法迭代列表stream().map()。但是,for(:)在迭代和修改列表时没有引发异常。

这是代码片段,如果它对任何人都有帮助:这里我正在迭代 a 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);
        }
于 2021-07-03T19:11:07.213 回答
0

如果使用 HashMap,在较新版本的 Java (8+) 中,您可以选择 3 个选项中的每一个:

public class UserProfileEntity {
    private String Code;
    private String mobileNumber;
    private LocalDateTime inputDT;
    // getters and setters here
}
HashMap<String, UserProfileEntity> upMap = new HashMap<>();


// remove by value
upMap.values().removeIf(value -> !value.getCode().contains("0005"));

// remove by key
upMap.keySet().removeIf(key -> key.contentEquals("testUser"));

// remove by entry / key + value
upMap.entrySet().removeIf(entry -> (entry.getKey().endsWith("admin") || entry.getValue().getInputDT().isBefore(LocalDateTime.now().minusMinutes(3)));
于 2021-11-28T09:17:05.533 回答
-1

你也可以使用递归

java中的递归是一个方法不断调用自身的过程。java中调用自身的方法称为递归方法。

于 2020-06-04T13:23:36.340 回答
-2

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

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

我不记得我是从哪里读到这篇文章的……为了公正起见,我会制作这个维基,希望有人能找到它,或者只是为了不赢得我不应该得到的代表。

于 2013-10-12T04:57:52.660 回答