Как избежать java.util.ConcurrentModificationException при повторении и удалении элементов из массива ArrayList

У меня есть ArrayList, который я хочу перебрать. Итерируя по нему, я должен удалить элементы одновременно. Очевидно, это вызывает java.util.ConcurrentModificationException.

Как лучше всего справиться с этой проблемой? Должен ли я сначала клонировать список?

Я удаляю элементы не в самом цикле, а в другой части кода.

Мой код выглядит так:

public class Test() {
    private ArrayList<A> abc = new ArrayList<A>();

    public void doStuff() {
        for (A a : abc) 
        a.doSomething();
    }

    public void removeA(A a) {
        abc.remove(a);
    }
}

a.doSomething может вызвать Test.removeA();

Ответ 1

Два варианта:

  • Создайте список значений, которые вы хотите удалить, добавив в этот список в цикле, затем вызовите originalList.removeAll(valuesToRemove) в конце
  • Используйте метод remove() на самом итераторе. Обратите внимание, что это означает, что вы не можете использовать расширенный цикл for.

В качестве примера второго варианта удалим любые строки длиной более 5 из списка:

List<String> list = new ArrayList<String>();
...
for (Iterator<String> iterator = list.iterator(); iterator.hasNext(); ) {
    String value = iterator.next();
    if (value.length() > 5) {
        iterator.remove();
    }
}

Ответ 2

Из JavaDocs в ArrayList

Итераторы, возвращаемые этим классом итератором и listIterator методы не работают быстро: если список структурно модифицирован в любом время после создания итератора, любым способом, кроме как через итератор самостоятельно удаляет или добавляет методы, итератор будет бросать ConcurrentModificationException.

Ответ 3

Вы должны просто просто повторить массив традиционным способом

Каждый раз, когда вы удаляете элемент из списка, элементы после этого будут продвигаться вперед. Если вы не меняете элементы, отличные от итерации, следующий код должен работать.

public class Test(){
    private ArrayList<A> abc = new ArrayList<A>();

    public void doStuff(){
        for(int i = (abc.size() - 1); i >= 0; i--) 
            abc.get(i).doSomething();
    }

    public void removeA(A a){
        abc.remove(a);
    }
}

Ответ 4

Вы пытаетесь удалить значение из списка в расширенном цикле for, что невозможно, даже если вы применили какой-то трюк (который вы сделали в своем коде). Лучше всего кодировать уровень итератора, как советуют другие.

Интересно, как люди не предложили традиционный для циклы подход.

for( int i = 0; i < lStringList.size(); i++ )
{
    String lValue = lStringList.get( i );
    if(lValue.equals("_Not_Required"))
    {
         lStringList.remove(lValue);
         i--; 
    }  
}

Это работает также.

Ответ 5

Выполнять цикл обычным способом, java.util.ConcurrentModificationException - это ошибка, связанная с доступными элементами.

Итак, попробуйте:

for(int i = 0; i < list.size(); i++){
    lista.get(i).action();
}

Ответ 6

Один из вариантов - изменить метод removeA на это -

public void removeA(A a,Iterator<A> iterator) {
     iterator.remove(a);
     }

Но это означало бы, что ваш doSomething() должен передать метод iterator методу remove. Не очень хорошая идея.

Можете ли вы сделать это в двухэтапном подходе:  В первом цикле, когда вы перебираете список, вместо удаления выбранных элементов отметьте их как удаляемые. Для этого вы можете просто скопировать эти элементы (мелкой копии) в другой List.

Затем, как только ваша итерация будет выполнена, просто выполните removeAll из первого списка все элементы во втором списке.

Ответ 7

Вот пример, когда я использую другой список для добавления объектов для удаления, а затем я использую stream.foreach для удаления элементов из исходного списка:

private ObservableList<CustomerTableEntry> customersTableViewItems = FXCollections.observableArrayList();
...
private void removeOutdatedRowsElementsFromCustomerView()
{
    ObjectProperty<TimeStamp> currentTimestamp = new SimpleObjectProperty<>(TimeStamp.getCurrentTime());
    long diff;
    long diffSeconds;
    List<Object> objectsToRemove = new ArrayList<>();
    for(CustomerTableEntry item: customersTableViewItems) {
        diff = currentTimestamp.getValue().getTime() - item.timestamp.getValue().getTime();
        diffSeconds = diff / 1000 % 60;
        if(diffSeconds > 10) {
            // Element has been idle for too long, meaning no communication, hence remove it
            System.out.printf("- Idle element [%s] - will be removed\n", item.getUserName());
            objectsToRemove.add(item);
        }
    }
    objectsToRemove.stream().forEach(o -> customersTableViewItems.remove(o));
}

Ответ 8

В Java 8 вы можете использовать интерфейс Collection и сделать это, вызвав метод removeIf:

yourList.removeIf((A a) -> a.value == 2);

Более подробную информацию можно найти здесь

Ответ 9

Сделайте что-то простое:

for (Object object: (ArrayList<String>) list.clone()) {
    list.remove(object);
}

Ответ 10

Вместо использования Для каждого цикла используйте обычный цикл for. например, приведенный ниже код удаляет все элементы в списке массивов, не давая исключение java.util.ConcurrentModificationException. Вы можете изменить условие в цикле в соответствии с вашим вариантом использования.

   for(int i=0;i<abc.size();i++)  {

          e.remove(i);
        }

Ответ 11

"Должен ли я сначала клонировать список?"

Это будет самое простое решение, удалите из клона и скопируйте его обратно после удаления.

Пример из моей игры rummikub:

SuppressWarnings("unchecked")
public void removeStones() {
  ArrayList<Stone> clone = (ArrayList<Stone>) stones.clone();
  // remove the stones moved to the table
  for (Stone stone : stones) {
      if (stone.isOnTable()) {
         clone.remove(stone);
      }
  }
  stones = (ArrayList<Stone>) clone.clone();
  sortStones();
}

Ответ 12

Альтернативное решение Java 8 с использованием потока:

        theList = theList.stream()
            .filter(element -> !shouldBeRemoved(element))
            .collect(Collectors.toList());

В Java 7 вы можете использовать вместо Guava:

        theList = FluentIterable.from(theList)
            .filter(new Predicate<String>() {
                @Override
                public boolean apply(String element) {
                    return !shouldBeRemoved(element);
                }
            })
            .toImmutableList();

Обратите внимание, что пример Guava приводит к неизменному списку, который может или не может быть тем, что вы хотите.

Ответ 13

Во время итерации списка, если вы хотите удалить элемент, это возможно. Давай посмотрим ниже мои примеры,

ArrayList<String>  names = new ArrayList<String>();
        names.add("abc");
        names.add("def");
        names.add("ghi");
        names.add("xyz");

У меня есть вышеуказанные имена списка Array. И я хочу удалить имя "def" из списка выше,

for(String name : names){
    if(name.equals("def")){
        names.remove("def");
    }
}

Приведенный выше код вызывает исключение ConcurrentModificationException, поскольку вы изменяете список во время итерации.

Таким образом, чтобы удалить имя "def" из Arraylist, сделав так,

Iterator<String> itr = names.iterator();            
while(itr.hasNext()){
    String name = itr.next();
    if(name.equals("def")){
        itr.remove();
    }
}

Приведенный выше код, через итератор, мы можем удалить имя "def" из Arraylist и попытаться напечатать массив, вы увидите вывод ниже.

Вывод: [abc, ghi, xyz]

Ответ 14

Если ваша цель - удалить все элементы из списка, вы можете выполнить итерацию по каждому элементу, а затем вызвать:

list.clear()

Ответ 15

Я опаздываю, я знаю, но я отвечаю на это, потому что я думаю, что это простое и элегантное решение:

List<String> listFixed = new ArrayList<String>();
List<String> dynamicList = new ArrayList<String>();

public void fillingList() {
    listFixed.add("Andrea");
    listFixed.add("Susana");
    listFixed.add("Oscar");
    listFixed.add("Valeria");
    listFixed.add("Kathy");
    listFixed.add("Laura");
    listFixed.add("Ana");
    listFixed.add("Becker");
    listFixed.add("Abraham");
    dynamicList.addAll(listFixed);
}

public void updatingListFixed() {
    for (String newList : dynamicList) {
        if (!listFixed.contains(newList)) {
            listFixed.add(newList);
        }
    }

    //this is for add elements if you want eraser also 

    String removeRegister="";
    for (String fixedList : listFixed) {
        if (!dynamicList.contains(fixedList)) {
            removeResgister = fixedList;
        }
    }
    fixedList.remove(removeRegister);
}

Все это для обновления из одного списка в другой, и вы можете сделать все из одного списка, а при обновлении метода вы проверяете оба списка и можете стирать или добавлять элементы между списками. Это означает, что оба списка всегда имеют одинаковый размер

Ответ 16

Используйте Iterator вместо Array List

Пусть набор будет преобразован в итератор с типом соответствия

И перейти к следующему элементу и удалить

Iterator<Insured> itr = insuredSet.iterator();
while (itr.hasNext()) { 
    itr.next();
    itr.remove();
}

Здесь важно перейти к следующему, так как для удаления элемента необходим индекс.

Ответ 17

Просто добавьте разрыв после вашего заявления ArrayList.remove(A)