Итерация по коллекции, избегая исключения ConcurrentModificationException при удалении объектов в цикле - PullRequest
1120 голосов
/ 22 октября 2008

Мы все знаем, что вы не можете сделать это:

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.

Ответы [ 23 ]

7 голосов
/ 16 апреля 2017

С традиционной для цикла

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++;
      }
2 голосов
/ 13 октября 2017

A 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);
   }
}
1 голос
/ 23 июня 2016

ConcurrentHashMap или ConcurrentLinkedQueue или ConcurrentSkipListMap может быть другой опцией, потому что они никогда не вызовут исключение ConcurrentModificationException, даже если вы удалите или добавите элемент.

1 голос
/ 19 ноября 2013

У меня есть предложение по проблеме выше. Нет необходимости во вторичном списке или дополнительном времени. Пожалуйста, найдите пример, который сделал бы то же самое, но по-другому.

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

Это позволит избежать исключения параллелизма.

1 голос
/ 14 марта 2019

Другой способ - создать копию вашего arrayList:

List<Object> l = ...

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

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

}

1 голос
/ 03 мая 2018

Лучший способ (рекомендуется) - использование пакета 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);
    }
0 голосов
/ 04 февраля 2016

В случае ArrayList: remove (int index) - если (index - позиция последнего элемента), он избегает без System.arraycopy() и не требует для этого времени.

Время копирования массива увеличивается, если (индекс уменьшается), кстати, элементы списка также уменьшаются!

лучший эффективный способ удаления - удаление его элементов в порядке убывания: while(list.size()>0)list.remove(list.size()-1); // принимает O (1) while(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 мсек
  • для индекса: 519 мсек --- лучший
  • для итератора: 1043 мсек
0 голосов
/ 20 декабря 2018

Я знаю, что этот вопрос предполагает только Collection, а не конкретнее List. Но для тех, кто читает этот вопрос и которые действительно работают со ссылкой List, вы можете вместо этого ConcurrentModificationException с while -циклом (изменяя его), если вы хотите избежать Iterator (либо если вы хотите избежать этого в целом, либо избегать его специально для достижения порядка зацикливания, отличного от остановки до конца для каждого элемента [который, я считаю, является единственным порядком, который может выполнять только Iterator)):

* Обновление: см. Комментарии ниже, которые поясняют, что аналогичное также возможно с традиционным для цикла.

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 - нет. Если бы не эта разница, тогда ссылка list могла бы быть Collection [и, следовательно, технически этот Ответ был бы тогда прямым ответом, а не тангенциальным ответом].

FWIWW тот же код все еще работает после изменения, чтобы начинаться с начала и с остановки на каждом элементе (точно так же, как 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;
    }
}
0 голосов
/ 26 сентября 2018

Я знаю, что этот вопрос слишком старый, чтобы быть о 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);
0 голосов
/ 17 сентября 2018

Пример модификации потока безопасной коллекции:

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();
            }
        }
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...