Iteracja poprzez kolekcję, unikanie ConcurrentModificationException podczas usuwania obiektów w pętli

Wszyscy wiemy, że nie możesz wykonać następujących czynności z powodu ConcurrentModificationException:

for (Object i : l) {
    if (condition(i)) {
        l.remove(i);
    }
}
Ale to najwyraźniej działa czasami, ale nie zawsze. Oto jakiś konkretny kod:
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);
}

To oczywiście skutkuje:

Exception in thread "main" java.util.ConcurrentModificationException

Mimo że wiele wątków tego nie robi. W każdym razie.

Jakie jest najlepsze rozwiązanie tego problemu? Jak mogę usunąć element z kolekcji w pętli bez rzucania tego wyjątku?

Używam też arbitralnego Collection tutaj, nie koniecznie ArrayList, więc nie można polegać na get.

Author: Ihor Patsian, 2008-10-21

27 answers

Iterator.remove() jest bezpieczny, można go używać w ten sposób:

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

Zauważ, że Iterator.remove() jest jedynym bezpiecznym sposobem modyfikacji kolekcji podczas iteracji; zachowanie jest nieokreślone, jeśli bazowa kolekcja jest modyfikowana w jakikolwiek inny sposób podczas iteracji jest w toku.

Źródło: docs.oracle > the Collection Interface


I podobnie, jeśli masz ListIterator i chcesz dodać elementy, możesz użyć ListIterator#add, na ten sam powód, dla którego możesz użyć Iterator#remove - jest zaprojektowany, aby na to pozwolić.


W Twoim przypadku próbowałeś usunąć z listy, ale to samo ograniczenie ma zastosowanie, jeśli próbujesz put do Map podczas iteracji jego treści.

 1633
Author: Bill K,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-10-17 08:26:52

To działa:

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

Założyłem, że ponieważ pętla foreach jest cukrem składniowym do iteracji, użycie iteratora nie pomoże... ale daje Ci to .remove() funkcjonalność.

 349
Author: Claudiu,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2019-01-03 10:36:58

W Javie 8 możesz użyć nowej metody removeIf . Zastosowany do twojego przykładu:

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

coll.removeIf(i -> i == 5);
 217
Author: assylias,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2019-01-03 10:37:29

Ponieważ pytanie zostało już udzielone, tzn. najlepszym sposobem jest użycie metody remove obiektu iteratora, przejdę do specyfiki miejsca, w którym wyrzucany jest błąd "java.util.ConcurrentModificationException".

Każda klasa kolekcji ma klasę prywatną, która implementuje interfejs iteratora i dostarcza metody takie jak next(), remove() i hasNext().

Kod do next wygląda mniej więcej tak...

public E next() {
    checkForComodification();
    try {
        E next = get(cursor);
        lastRet = cursor++;
        return next;
    } catch(IndexOutOfBoundsException e) {
        checkForComodification();
        throw new NoSuchElementException();
    }
}

Tutaj metoda checkForComodification jest zaimplementowana jako

final void checkForComodification() {
    if (modCount != expectedModCount)
        throw new ConcurrentModificationException();
}

Tak, jak ty można zobaczyć, jeśli jawnie próbujesz usunąć element z kolekcji. Powoduje to, że modCount różni się od expectedModCount, w wyniku czego powstaje wyjątek ConcurrentModificationException.

 43
Author: Ashish,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-06-19 10:22:32

Możesz użyć iteratora bezpośrednio, jak wspomniałeś, albo zachować drugą kolekcję i dodać każdy element, który chcesz usunąć do nowej kolekcji, a następnie removeAll na końcu. Pozwala to na zachowanie bezpieczeństwa typu pętli for-each kosztem zwiększonego zużycia pamięci i czasu procesora (nie powinno to być wielkim problemem, chyba że masz naprawdę duże listy lub naprawdę stary komputer)

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);
}
 27
Author: RodeoClown,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-01-24 19:05:19

W takich przypadkach powszechnym trikiem jest (was?) do tyłu:

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

To powiedziawszy, Jestem bardziej niż szczęśliwy, że masz lepsze sposoby w Javie 8, np. removeIf lub filter na streamach.

 18
Author: Landei,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-08-29 09:56:15

Ta sama odpowiedź co Klaudiusz z pętlą for:

for (Iterator<Object> it = objects.iterator(); it.hasNext();) {
    Object object = it.next();
    if (test) {
        it.remove();
    }
}
 16
Author: Antzi,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 10:31:36

Wykonaj kopię istniejącej listy i wykonaj iterację nad nową.

for (String str : new ArrayList<String>(listOfStr))     
{
    listOfStr.remove(/* object reference or index */);
}
 12
Author: Priyank Doshi,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-06-26 05:28:35

Z kolekcjami Eclipse , Metoda removeIf zdefiniowana na MutableCollection będzie działać:

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

W składni Java 8 Lambda można to zapisać w następujący sposób:

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

Wywołanie Predicates.cast() jest tutaj konieczne, ponieważ domyślna metoda removeIf została dodana w interfejsie java.util.Collection W Java 8.

Uwaga: jestem committerem dla kolekcji Eclipse .

 12
Author: Donald Raab,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2019-11-10 00:03:12

Ludzie twierdzą, że jeden nie może usunąć ze zbioru iterowanego przez pętlę foreach. Chciałem tylko zaznaczyć, że jest technicznie niepoprawne i dokładnie opisać (wiem, że pytanie OP jest na tyle zaawansowane, że o tym Nie wiem) kod leżący za tym założeniem:

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

Nie chodzi o to, że nie możesz usunąć z iteracji Colletion, raczej o to, że nie możesz kontynuować iteracji, gdy to zrobisz. Stąd break w powyższym kodzie.

Przepraszam, jeśli to odpowiedź jest nieco specjalistycznym przypadkiem użycia i bardziej pasuje do oryginalnego wątku , z którego tu przybyłem, ten jest oznaczony jako DUPLIKAT (mimo że ten wątek wydaje się bardziej niuansowy) tego i zablokowany.

 10
Author: John,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-01-21 15:09:55

Z tradycyjną pętlą 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++;   
}
 8
Author: Lluis Felisart,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-01-25 22:29:32

ConcurrentHashMap lub ConcurrentLinkedQueuelub ConcurrentSkipListMap mogą być inną opcją, ponieważ nigdy nie wyrzucą one żadnego ConcurrentModificationException, nawet jeśli usuniesz lub dodasz element.

 3
Author: Yessy,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-06-23 11:18:58

Innym sposobem jest użycie kopii arrayList tylko do iteracji:

List<Object> l = ...
    
List<Object> iterationList = ImmutableList.copyOf(l);
    
for (Object curr : iterationList) {
    if (condition(curr)) {
        l.remove(curr);
    }
}
 3
Author: Nestor Milyaev,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-11-11 16:40:16

A ListIterator umożliwia dodawanie lub usuwanie elementów z listy. Załóżmy, że masz listę Car obiektów:

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);
   }
}
 2
Author: james.garriss,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-10-13 15:16:20

Mam sugestię na powyższy problem. Nie potrzeba dodatkowej listy ani dodatkowego czasu. Proszę znaleźć przykład, który zrobiłby to samo, ale w inny sposób.

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

Pozwoli to uniknąć wyjątku dla współbieżności.

 1
Author: Nandhan Thiravia,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-12-11 18:41:25

Wiem, że to pytanie jest za Stare, aby być o Javie 8, ale dla tych, którzy używają Javy 8, możesz łatwo użyć 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);
 1
Author: pedram bashiri,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-09-26 20:15:21

Najlepszym sposobem (zalecanym) jest użycie Javy.util.Pakiet współbieżny . Przez korzystając z tego pakietu możesz łatwo uniknąć tego wyjątku . refer Zmodyfikowany Kod

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
Author: jagdish khetre,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-01-21 14:52:28

W przypadku ArrayList: remove (int index) - jeśli (indeks jest pozycją ostatniego elementu) unika się bez System.arraycopy() i nie zajmuje na to czasu.

Czas arraycopy wzrasta, jeśli (indeks maleje), przy okazji elementy listy również maleją!

Najlepszym skutecznym sposobem usuwania jest-usuwanie jego elementów w kolejności malejącej: while(list.size()>0)list.remove(list.size()-1);//takes O (1) while(list.size()>0)list.remove(0);//bierze 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
  • dla pętli indeksu: 1090 msec
  • dla indeksu desc: 519 msec - - - the best
  • dla iteratora: 1043 msec
 0
Author: Nurlan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-02-04 17:25:28
for (Integer i : l)
{
    if (i.intValue() == 5){
            itemsToRemove.add(i);
            break;
    }
}

Haczyk jest po usunięciu elementu z listy, jeśli pominiesz wewnętrzny iterator.next () call. to nadal działa! Chociaż nie proponuję pisać kodu w ten sposób, pomaga to zrozumieć koncepcję stojącą za nim: -)

Zdrówko!
 0
Author: Srinivasan Thoyyeti,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-06-30 07:30:34

Przykład modyfikacji zbioru wątków:

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();
            }
        }
    }
}
 0
Author: Yazon2006,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-09-17 09:03:02

Wiem, że to pytanie zakłada tylko Collection, a nie dokładniej jakiekolwiek List. Ale dla tych, którzy czytają to pytanie, którzy rzeczywiście pracują z odniesieniem List, możesz uniknąć ConcurrentModificationException za pomocą while-loop (podczas modyfikowania w nim) zamiast tego, jeśli chcesz uniknąć Iterator (albo jeśli chcesz tego uniknąć w ogóle, albo unikaj tego specjalnie, aby osiągnąć kolejność pętli różniącą się od początku do końca zatrzymywania się na każdym elemencie [co moim zdaniem jest jedynym porządkiem Iterator, który może do]):

*Aktualizacja: Zobacz komentarze poniżej, które wyjaśniają analogiczny jest również osiągalne z tradycyjne - dla-pętli.

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

brak modyfikacji współbieżności z tego kodu.

Widzimy, że pętla nie zaczyna się od początku, a nie kończy się na każdym elemencie (co, jak sądzę, {7]} sam nie może zrobić).

FWIW widzimy również get wywołanie na list, czego nie można zrobić, gdyby jego odniesienie było tylko Collection (zamiast bardziej specyficznego List - typu Collection) - List interfejs zawiera get, ale Collection interfejs nie. Jeśli nie dla tej różnicy, to list odniesienie może być zamiast Collection [i dlatego technicznie odpowiedź ta byłaby odpowiedzią bezpośrednią zamiast odpowiedzi stycznej].

FWIWW ten sam kod nadal działa po zmodyfikowaniu, aby zacząć od początku przy każdym elemencie (tak jak Iterator kolejność):

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
Author: cellepo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-12-20 00:49:18

Jednym z rozwiązań może być obrócenie listy i usunięcie pierwszego elementu, aby uniknąć ConcurrentModificationException lub 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);
 0
Author: Rahul Vala,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2019-07-31 20:16:13

Spróbuj tego (usuwa wszystkie elementy na liście, które są równe i):

for (Object i : l) {
    if (condition(i)) {
        l = (l.stream().filter((a) -> a != i)).collect(Collectors.toList());
    }
}
 0
Author: Oleg Tatarchuk,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2019-11-13 19:15:38

Możesz również użyć rekurencji

Rekurencja w Javie jest procesem, w którym metoda wywołuje się w sposób ciągły. Metoda w języku java wywołująca samą siebie nazywana jest metodą rekurencyjną.

 0
Author: Firas Chebbah,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-06-04 13:23:36

Teraz możesz usunąć poniższym kodem

l.removeIf(current -> current == 5);
 0
Author: Adil Karaöz,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-09-22 14:22:27

Możesz użyć pętli 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();
    } 
}
 0
Author: Oguzhan Cevik,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-12-28 10:40:17

To może nie jest najlepszy sposób, ale w większości małych przypadków powinien być akceptowalny:

"Utwórz drugą pustą tablicę i dodaj tylko te, które chcesz zachować"

nie pamiętam skąd to czytałem... dla justiness zrobię tę wiki w nadziei, że ktoś ją znajdzie lub po prostu nie zasłużyć na reputację, na którą nie zasługuję.

 -2
Author: ajax333221,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-10-12 04:57:52