我们都知道,由于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

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

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

这里我也用了一个任意的集合,不一定是数组列表,所以你不能依赖get。


如此:

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

我认为由于foreach循环是迭代的语法糖,使用迭代器不会有帮助…但是它给了你这个。remove()功能。


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()是在迭代期间修改集合的唯一安全方法;如果在进行迭代时以任何其他方式修改底层集合,则行为未指定。

来源:文档。oracle >采集接口


类似地,如果你有一个ListIterator并想要添加项目,你可以使用ListIterator#add,同样的原因你可以使用Iterator#remove -它的设计允许这样做。


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


您可以像前面提到的那样直接使用迭代器,也可以保留第二个集合,并将想要删除的每个项添加到新集合中,然后在最后删除所有项。这允许您以增加内存使用和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);
}

因为问题已经回答即最好的方式是使用迭代器对象的删除方法,我想去的地方的细节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();
}

因此,如您所见,如果您显式地试图从集合中删除一个元素。它导致modCount与expectedModCount不同,导致异常ConcurrentModificationException。


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

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

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

这里必须调用predicasts .cast(),因为Java 8中的Java .util. collection接口上添加了默认的removeIf方法。

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


和Claudius的for循环答案一样:

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

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

创建第二个空数组,只添加你想保留的数组

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


对于上述问题,我有一个建议。不需要辅助列表或任何额外的时间。请找到一个例子,它会做同样的事情,但以不同的方式。

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

这将避免并发异常。


在Java 8中,你可以使用新的removeIf方法。应用于你的例子:

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

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

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

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

也就是说,我很高兴你在Java 8中有更好的方法,例如removeIf或过滤流。


在case ArrayList:remove(int index)- if(index是最后一个元素的位置)它会避免没有System.arraycopy(),并且不会花费时间。

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

最有效的删除方法是-按降序删除其元素: 而(list.size () > 0) list.remove (list.size() 1); / /需要O (1) 而(list.size () > 0) list.remove(0); / /需要O(阶乘(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 msec


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


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

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

干杯!


使用传统的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++;   
}

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

人们断言一个对象不能从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
    }
}

这并不是说您不能从迭代的collection中删除,而是您一旦删除了collection就不能继续迭代。因此上面的代码出现了中断。

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


最好的方法(推荐)是使用java.util.concurrent包。通过 使用这个包可以很容易地避免这种异常。请参考 修改代码:

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

线程安全集合修改示例:

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

我知道这个问题对于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);

I know this question assumes just a Collection, and not more specifically any List. But for those reading this question who are indeed working with a List reference, you can avoid ConcurrentModificationException with a while-loop (while modifying within it) instead if you want to avoid Iterator (either if you want to avoid it in general, or avoid it specifically to achieve a looping order different from start-to-end stopping at each element [which I believe is the only order Iterator itself can do]):

*更新:参见下面的注释,说明类似的情况也可以用传统的for循环实现。

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在list上被调用,如果它的引用只是Collection(而不是更具体的list类型的Collection),则无法做到这一点——list接口包括get,但Collection接口不包括。如果不是因为这个区别,那么列表引用可以是一个集合[因此从技术上讲,这个答案将是一个直接答案,而不是一个切向答案]。

FWIWW相同的代码在修改为start at beginning at stop at每个元素后仍然有效(就像Iterator order一样):

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

另一种方法是使用数组列表的副本进行迭代:

List<Object> l = ...
    
List<Object> iterationList = ImmutableList.copyOf(l);
    
for (Object curr : iterationList) {
    if (condition(curr)) {
        l.remove(curr);
    }
}

一种解决方案是旋转列表并删除第一个元素,以避免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);

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

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

你也可以使用递归

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


现在,你可以用下面的代码删除

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

你可以使用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();
    } 
}

Java并发修改异常

单线程

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

多线

复制/转换并遍历另一个集合。小型收藏 同步(约) 线程安全收集[关于]


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

以下是代码片段,如果对任何人有帮助的话: 这里我在一个<BuildEntity>的ArrayList上迭代,并使用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);
        }

如果使用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)));

当另一个线程也修改集合时,迭代器并不总是有用。我尝试了很多方法,但后来意识到手动遍历集合要安全得多(向后删除):

for (i in myList.size-1 downTo 0) {
    myList.getOrNull(i)?.also {
       if (it == 5)
          myList.remove(it)
    }
}