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

203

У меня есть 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();

Belphegor
источник

Ответы:

325

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

  • Создайте список значений, которые вы хотите удалить, добавив в этот список в цикле, а затем вызовите 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
Я должен был упомянуть, что я удаляю элементы в другой части кода, а не сам цикл.
RoflcoptrException
@Roflcoptr: Ну, трудно ответить, не видя, как взаимодействуют два бита кода. По сути, вы не можете этого сделать. Не очевидно, поможет ли сначала клонирование списка, не видя, как все это сочетается. Можете ли вы дать более подробную информацию в вашем вопросе?
Джон Скит
Я знаю, что клонирование списка поможет, но я не знаю, хороший ли это подход. Но я добавлю еще немного кода.
RoflcoptrException
2
Это решение также приводит к появлению java.util.ConcurrentModificationException, см. Stackoverflow.com/a/18448699/2914140 .
CoolMind
1
@CoolMind: без нескольких потоков этот код должен быть в порядке.
Джон Скит
17

Из JavaDocs ArrayList

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

Варун Ахар
источник
6
и где ответ на вопрос?
Аделин
Как говорится, кроме как через собственные методы удаления или добавления итератора
Варун Ахар
14

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

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

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

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

suhas0sn07
источник
2
Это не правильно !!! когда вы удаляете элемент, следующий занимает свою позицию, и, пока я увеличиваю, следующий элемент не проверяется в следующей итерации. В этом случае вы должны пойти для (int i = lStringList.size (); i> -1; i--)
Johntor
1
Согласен! Альтернативой является выполнение i--; в случае если в течение цикла.
suhas0sn07
Я думаю, что этот ответ был отредактирован для решения проблем в вышеприведенных комментариях, так что теперь он работает нормально, по крайней мере, для меня.
Кира Резари
11

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

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

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);
    }
}
Маркус
источник
10

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

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

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

ggeo
источник
6

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

Поэтому постарайтесь:

for(int i = 0; i < list.size(); i++){
    lista.get(i).action();
}
Tacila
источник
Вы избежали java.util.ConcurrentModificationException, не удаляя что-либо из списка. Tricky. :) Вы не можете назвать это «нормальным способом» для перебора списка.
Жолт Скай
6

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

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]

Индра К
источник
Иначе, мы можем использовать параллельный список, который доступен в параллельном пакете, так что вы можете выполнять операции удаления и добавления во время итерации. Например, смотрите приведенный ниже фрагмент кода. ArrayList <String> names = new ArrayList <String> (); CopyOnWriteArrayList <String> copyNames = new CopyOnWriteArrayList <String> (names); for (имя строки: copyNames) {if (name.equals ("def")) {copyNames.remove ("def"); }}
Индра К
CopyOnWriteArrayList будет самой дорогой операцией.
Индра К
5

Одним из вариантов является изменение removeAметода для этого -

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

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

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

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

Bhaskar
источник
Отлично, я использовал тот же подход, хотя я повторяю дважды. это упрощает ситуацию и не дает никаких сопутствующих проблем :)
Pankaj Nimgade
1
Я не вижу, что у Iterator есть метод remove (a). Метод remove () не имеет аргументов docs.oracle.com/javase/8/docs/api/java/util/Iterator.html что мне не хватает?
Кодер
5

Вот пример, в котором я использую другой список для добавления объектов для удаления, а затем я использую 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));
}
Serup
источник
Я думаю, что вы выполняете дополнительную работу, выполняя два цикла, в худшем случае циклы будут из всего списка. Было бы проще и дешевле сделать это всего за один цикл.
Луис Карлос
Я не думаю, что вы можете удалить объект из первого цикла, следовательно, необходим дополнительный цикл удаления, также цикл удаления - это только объекты для удаления - возможно, вы могли бы написать пример только с одним циклом, я бы хотел его видеть - спасибо @ ЛуисКарлос
serup
Как вы говорите с этим кодом, вы не можете удалить любой элемент внутри цикла for, потому что это вызывает исключение java.util.ConcurrentModificationException. Однако вы можете использовать основные для. Здесь я пишу пример, используя часть вашего кода.
Луис Карлос
1
for (int i = 0; i <CustomersTableViewItems.size (); i ++) {diff = currentTimestamp.getValue (). getTime () - CustomersTableViewItems.get (i) .timestamp.getValue (). getTime (); diffSeconds = diff / 1000% 60; if (diffSeconds> 10) {CustomersTableViewItems.remove (i--); }} Это важно, потому что вы не хотите пропускать какой-либо элемент. Также вы можете использовать метод removeIf (фильтр Predicate <? Super E>), предоставляемый классом ArrayList. Надеюсь, что эта помощь
Луис Карлос
1
Исключение происходит потому, что в цикле for существует активная ссылка на итератор списка. В обычном для, нет ссылки, и у вас есть больше возможностей для изменения данных. Надеюсь, что это поможет
Луис Карлос
3

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

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

          e.remove(i);
        }
Шубхам Чопра
источник
2

Сделайте что-нибудь простое, например:

for (Object object: (ArrayList<String>) list.clone()) {
    list.remove(object);
}
XLSX
источник
2

Альтернативное решение 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 приводит к неизменному списку, который может или не может быть тем, что вы хотите.

Жолт Скай
источник
1

Вы также можете использовать CopyOnWriteArrayList вместо ArrayList. Это последний рекомендуемый подход, начиная с версии 1.5.

Pathikreet
источник
1

В моем случае принятый ответ не работает, он останавливает исключение, но вызывает некоторые противоречия в моем списке. Следующее решение отлично работает для меня.

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

for (String value: list) {
   if (value.length() > 5) { // your condition
       itemsToRemove.add(value);
   }
}
list.removeAll(itemsToRemove);

В этом коде я добавил элементы для удаления в другой список, а затем использовал list.removeAllметод для удаления всех необходимых элементов.

Асад Али Чоудхри
источник
0

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

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

Пример из моей игры в руммикуб:

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();
}
Арьен Роденхуйс
источник
2
Downvoters должны по крайней мере оставить комментарий, прежде чем downvoting.
OneWorld
2
Нет ничего изначально неправильного в этом ответе, возможно, stones = (...) clone.clone();это излишне. Не stones = clone;сделал бы то же самое?
Викингстеве
Согласен, второе клонирование не нужно. Вы можете еще больше упростить это, перебирая клон и удаляя элементы непосредственно из stones. Таким образом, вам даже не нужна cloneпеременная: for (Stone stone : (ArrayList<Stone>) stones.clone()) {...
Zsolt Sky
0

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

list.clear()
Gibolt
источник
0

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

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

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

Беккер Аргуэльо Флорес
источник
0

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

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

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

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

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

user8009263
источник
0

Как насчет

import java.util.Collections;

List<A> abc = Collections.synchronizedList(new ArrayList<>());
joseluisbz
источник
-3

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

Себастьян Альтамирано
источник
Не могли бы вы добавить объяснение?
xskxzr