Java For Loop List Deletion Concurrent Modification Exception [duplicate]

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

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

ConcurrentModificationException и т. д. ... это, по-видимому, работает иногда, но не всегда. Вот какой-то конкретный код:

public static void main(String[] args) {
    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));
    }

    for (Integer i : l) {
        if (i.intValue() == 5) {
            l.remove(i);
        }
    }

    System.out.println(l);
}

Это, конечно же, приводит к:

Exception in thread "main" java.util.ConcurrentModificationException

... хотя несколько потоков не делают этого ... Во всяком случае.

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

Я также использую произвольное Collection здесь, не обязательно ArrayList, поэтому вы не можете полагаться на get.

1030
задан 28 March 2016 в 17:25

20 ответов

это может быть не лучшим образом, но для большинства маленьких случаев это должно быть приемлемым:

"создать второй пустой массив и добавить только те, которые вы хотите сохранить"

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

-3
ответ дан 15 August 2018 в 17:04

С помощью Java 8 вы можете использовать новый метод removeIf. Применяется к вашему примеру:

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

coll.removeIf(i -> i.intValue() == 5);
156
ответ дан 15 August 2018 в 17:04
  • 1
    Ооооо! Я надеялся, что что-то в Java 8 или 9 может помочь. Это все еще кажется довольно многословным, но мне все еще нравится. – James T Snell 23 September 2015 в 21:25
  • 2
    Использует ли equals () в этом случае тоже? – Anmol Gupta 11 December 2015 в 12:57
  • 3
    кстати, removeIf использует петли Iterator и while. Вы можете увидеть его в java 8 java.util.Collection.java – omerhakanbilici 31 October 2016 в 18:10
  • 4
    @omerhakanbilici Некоторые реализации, такие как ArrayList, переопределяют его по соображениям производительности. Тот, о котором вы говорите, является только реализацией по умолчанию. – Didier L 31 March 2017 в 11:33
Collection<Integer> l = new ArrayList<Integer>();//Do the collection thing...

l.removeIf(i -> i == 5);      //iterates through the collection and removes every occurence of 5

Лямбда-выражения и методы сбора в Jdk 8 входят в Handy и добавляют некоторый синтаксический сахар

0
ответ дан 15 August 2018 в 17:04
  • 1
    Хотя этот фрагмент кода может быть решением, , включая объяснение , действительно помогает улучшить качество вашего сообщения. Помните, что вы отвечаете на вопрос читателей в будущем, и эти люди могут не знать причин вашего предложения кода. – Narendra Jadhav 19 July 2018 в 09:15
  • 2
    Спасибо, скорректировано. – Barnabas Ukwuani 19 July 2018 в 18:12

С Eclipse Collections (ранее GS Collections) метод removeIf, определенный в MutableCollection, будет работать:

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

Здесь необходим вызов Predicates.cast(), потому что по умолчанию removeIf был добавлен метод removeIf на интерфейсе java.util.Collection в Java 8.

Примечание. Я - коммиттер для Коллекции Eclipse .

11
ответ дан 15 August 2018 в 17:04

Это работает:

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

Я предположил, что поскольку цикл foreach является синтаксическим сахаром для итерации, использование итератора не помогло бы ... но оно дает вам эту функциональность .remove(). [ ! d1]

320
ответ дан 15 August 2018 в 17:04
  • 1
    foreach loop - синтаксический сахар для итерации. Однако, как вы указали, вам нужно вызвать remove на итераторе, который foreach не дает вам доступа. Следовательно, причина, по которой вы не можете удалить в цикле foreach (даже если вы фактически используете итератор под капотом) – madlep 22 October 2008 в 04:30
  • 2
    +1, например, код для использования iter.remove () в контексте, который не имеет ответа Билла К. [непосредственно]. – Eddified 3 October 2012 в 22:01
  • 3
    Я удаляю итератор, как вы делали здесь, но я все равно продолжаю получать такую ​​же ошибку. Есть идеи? – Gokhan Arik 6 May 2014 в 06:49

В ответ на @assylias вы также можете использовать новый поток api, если используете Java 8:

List<Integer> l = Arrays.asList(4, 5, 6);

static boolean condition(Integer i) {
    return i == 5;
}

static Predicate<Integer> predicate = YourClassName::condition;

l.stream()
    .filter(predicate.negate())
    .forEach(System.out::println);

Если вы инвертируете условие, решение еще более кратким, поскольку вам не нужно negate() предикат, что позволяет использовать только ссылку на метод:

List<Integer> l = Arrays.asList(4, 5, 6);

static boolean condition(Integer i) {
    return i != 5;    // <-- condition has been negated
}

l.stream()
    .filter(YourClassName::condition)
    .forEach(System.out::println);

Одна из красавиц этого заключается в том, что поток оценивается лениво, т. е. операция filter() фактически не оценивается до тех пор, пока она не будет использована терминальной операцией, такой как forEach(). Подробнее об этом можно узнать в ответе Oracle .

14
ответ дан 15 August 2018 в 17:04
  • 1
    Это только отфильтровывает элементы, которые в конечном итоге либо возвращаются, либо потребляются через побочные эффекты. Он не изменяет существующий, фактический список, который, я думаю, является тем, что предназначено. Этот ответ фактически описывает, как элемент можно удалить с помощью Java 8. – Makoto 17 September 2015 в 02:11
[F1]
-1
ответ дан 15 August 2018 в 17:04
  • 1
    Этот вопрос касается удаления какого-либо определенного элемента из списка, а не его полного удаления, поэтому это не отвечает на вопрос. – Radiodef 9 August 2018 в 03:24

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

38
ответ дан 15 August 2018 в 17:04
  • 1
    Очень интересно. Спасибо! Я часто сам не вызываю remove (), вместо этого предпочитаю очищать коллекцию после итерации через нее. Не сказать, что это хорошая модель, именно то, что я делал в последнее время. – James T Snell 23 September 2015 в 21:27
Лучшим способом (рекомендуется) является использование пакета 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);
    }
1
ответ дан 15 August 2018 в 17:04

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
ответ дан 15 August 2018 в 17:04

Люди утверждают, что нельзя удалить из коллекции, которая повторяется в цикле foreach. Я просто хотел указать, что это технически некорректно и точно описывает (я знаю, что вопрос 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 в коде выше.

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

4
ответ дан 15 August 2018 в 17:04

В таких случаях общий трюк (был?) для возврата назад:

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

Тем не менее, я более чем счастлив, что у вас есть лучшие способы в Java 8, например. removeIf или filter в потоках.

17
ответ дан 15 August 2018 в 17:04
  • 1
    Это хороший трюк. Но он не будет работать с неиндексированными коллекциями, такими как наборы, и было бы очень медленно говорить о связанных списках. – Claudiu 29 August 2014 в 21:12
  • 2
    @Claudiu Да, это определенно только для ArrayList s или подобных коллекций. – Landei 30 August 2014 в 20:47
  • 3
    Я использую ArrayList, это отлично работает, спасибо. – StarSweeper 21 February 2018 в 10:30
  • 4
    индексы велики. Если это так часто, почему вы не используете for(int i = l.size(); i-->0;) {? – John 27 April 2018 в 23:20

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

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++;
      }
6
ответ дан 15 August 2018 в 17:04

Вы можете перебирать список, используя for-loop, и вам нужно вызвать list.remove (0). Вам нужно с жестким кодом индексировать индексный параметр remove с нулем. См. Также этот ответ:

List<Integer> list = new ArrayList<Integer>();

list.add(1);
list.add(2);
list.add(3);
list.add(4);
int list_size = list.size();
for (int i = 0; i < list_size; i++) {
    list.remove(0);
}
-1
ответ дан 15 August 2018 в 17:04
  • 1
    Вопрос касается цикла foreach. Вы должны проверить их :) – Gibolt 23 March 2018 в 19:30
  • 2
    Это пустая трата времени. [F1]. – Sedrick 1 August 2018 в 22:06
  • 3
    Этот вопрос касается удаления какого-либо определенного элемента из списка, а не его полного удаления, поэтому это не отвечает на вопрос. – Radiodef 9 August 2018 в 03:23

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

Время 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 мсек для индекса desc: 519 мс --- лучший для итератора: 1043 мс
0
ответ дан 15 August 2018 в 17:04

Сделайте копию существующего списка и перейдете к новой копии.

for (String str : new ArrayList<String>(listOfStr))     
{
    listOfStr.remove(/* object reference or index */);
}
6
ответ дан 15 August 2018 в 17:04
  • 1
    Создание копии звучит как пустая трата ресурсов. – Antzi 21 August 2013 в 17:35
  • 2
    @Antzi Это зависит от размера списка и плотности объектов внутри. Еще ценное и действенное решение. – mre 4 May 2016 в 17:14

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

public static void main(String[] args)
{
    Collection<Integer> l = new ArrayList<Integer>();
    Collection<Integer> itemsToRemove = 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));
    }
    for (Integer i : l)
    {
        if (i.intValue() == 5)
            itemsToRemove.add(i);
    }

    l.removeAll(itemsToRemove);
    System.out.println(l);
}
22
ответ дан 15 August 2018 в 17:04
  • 1
    это то, что я обычно делаю, но явный итератор - это более элегантное решение, которое я чувствую. – Claudiu 22 October 2008 в 04:51
  • 2
    Достаточно справедливо, если вы не делаете ничего с итератором - при его открытии упрощается делать такие вещи, как вызов .next () дважды за цикл и т. Д. Не большая проблема, но может вызвать проблемы, если вы делаете что-то более сложное, чем просто просмотр списка для удаления записей. – RodeoClown 22 October 2008 в 04:58
  • 3
    @RodeoClown: в исходном вопросе Claudiu удаляет из коллекции, а не итератор. – matt b 22 October 2008 в 19:29
  • 4
    Удаление из итератора удаляется из основной коллекции ... но то, что я говорил в последнем комментарии, заключается в том, что если вы делаете что-то более сложное, чем просто искать удаленные в цикле (например, обрабатывать правильные данные), используя итератор, можете сделать некоторые ошибки легче сделать. – RodeoClown 22 October 2008 в 23:35
  • 5
    Если это простые значения удаления, которые не нужны, и цикл делает только одно, использование итератора напрямую и вызов .remove () абсолютно прекрасен. – RodeoClown 22 October 2008 в 23:36
for (Integer i : l)
{
    if (i.intValue() == 5){
            itemsToRemove.add(i);
            break;
    }
}

Ловушка - это удаление элемента из списка, если вы пропустите внутренний вызов iterator.next (). он все еще работает! Хотя я не предлагаю писать такой код, он помогает понять концепцию: -)

Cheers!

0
ответ дан 15 August 2018 в 17:04

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

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

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

0
ответ дан 15 August 2018 в 17:04
  • 1
    В вопросе явно указано, что ОП не требуется с помощью ArrayList и, следовательно, не может полагаться на get(). В противном случае, вероятно, хороший подход. – kaskelotti 13 April 2014 в 16:09

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

0
ответ дан 15 August 2018 в 17:04

Другие вопросы по тегам:

Похожие вопросы: