Come evitare java.util.ConcurrentModificationException durante l'iterazione e la rimozione di elementi da una matrice


203

Ho un ArrayList su cui voglio scorrere. Durante l'iterazione su di esso devo rimuovere gli elementi allo stesso tempo. Ovviamente questo genera a java.util.ConcurrentModificationException.

Qual è la migliore pratica per gestire questo problema? Devo prima clonare l'elenco?

Rimuovo gli elementi non nel ciclo stesso ma un'altra parte del codice.

Il mio codice è simile al seguente:

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.doSomethingpotrebbe chiamare Test.removeA();


Risposte:


325

Due opzioni:

  • Crea un elenco di valori che desideri rimuovere, aggiungendolo all'elenco all'interno del ciclo, quindi chiama originalList.removeAll(valuesToRemove)alla fine
  • Utilizzare il remove()metodo sull'iteratore stesso. Si noti che ciò significa che non è possibile utilizzare il ciclo avanzato per.

Come esempio della seconda opzione, rimuovere qualsiasi stringa con una lunghezza maggiore di 5 da un elenco:

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
Avrei dovuto menzionare che rimuovo gli elementi in un'altra parte del codice e non il ciclo stesso.
RoflcoptrException,

@Roflcoptr: Beh, è ​​difficile rispondere senza vedere come interagiscono i due bit di codice. Fondamentalmente, non puoi farlo. Non è ovvio se la clonazione dell'elenco possa aiutare, senza vedere come tutto si blocca. Puoi fornire maggiori dettagli nella tua domanda?
Jon Skeet,

So che la clonazione dell'elenco sarebbe di aiuto, ma non so se sia un buon approccio. Ma aggiungerò altro codice.
RoflcoptrException,

2
Questa soluzione porta anche a java.util.ConcurrentModificationException, vedi stackoverflow.com/a/18448699/2914140 .
CoolMind

1
@CoolMind: senza più thread, questo codice dovrebbe andare bene.
Jon Skeet,

17

Dal JavaDocs dell'ArrayList

Gli iteratori restituiti dai metodi iteratore e listIterator di questa classe sono fail-fast: se l'elenco viene modificato strutturalmente in qualsiasi momento dopo la creazione dell'iteratore, in alcun modo tranne che attraverso i metodi di rimozione o aggiunta dell'iteratore stesso , l'iteratore genererà un'eccezione ConcurrentModificationException.


6
e dov'è la risposta alla domanda?
Adelin,

Come si dice, tranne attraverso i metodi di rimozione o aggiunta
dell'iteratore

14

Stai provando a rimuovere il valore dall'elenco in "for loop" avanzato, il che non è possibile, anche se applichi qualche trucco (che hai fatto nel tuo codice). Il modo migliore è codificare il livello di iteratore come altri consigliato qui.

Mi chiedo come le persone non abbiano suggerito l'approccio tradizionale per loop.

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

Funziona anche questo.


2
Questo non è corretto !!! quando rimuovi un elemento, il successivo prende la sua posizione e mentre io aumento l'elemento successivo non viene verificato nella successiva iterazione. In questo caso dovresti scegliere (int i = lStringList.size (); i> -1; i--)
Johntor,

1
Essere d'accordo! L'alternativa è eseguire i--; in if condizione all'interno per loop.
suhas0sn07

Penso che questa risposta sia stata modificata per risolvere i problemi nei commenti precedenti, quindi com'è ora funziona benissimo, almeno per me.
Kira Resari,

11

Dovresti semplicemente eseguire l'iterazione dell'array nel modo tradizionale

Ogni volta che rimuovi un elemento dall'elenco, gli elementi successivi verranno spostati in avanti. Finché non si modificano elementi diversi da quello iterante, il codice seguente dovrebbe funzionare.

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

In Java 8 è possibile utilizzare l'interfaccia di raccolta e farlo chiamando il metodo removeIf:

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

Ulteriori informazioni possono essere trovate qui


6

Esegui il ciclo in modo normale, si java.util.ConcurrentModificationExceptiontratta di un errore relativo agli elementi a cui si accede.

Allora prova:

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

Hai evitato java.util.ConcurrentModificationExceptiondi non rimuovere nulla dall'elenco. Difficile. :) Non puoi davvero chiamarlo "il modo normale" per iterare un elenco.
Zsolt Sky

6

Durante l'iterazione dell'elenco, è possibile rimuovere l'elemento. Vediamo di seguito i miei esempi,

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

Ho i nomi precedenti dell'elenco di array. E voglio rimuovere il nome "def" dall'elenco sopra,

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

Il codice sopra genera l' eccezione ConcurrentModificationException perché si sta modificando l'elenco durante l'iterazione.

Quindi, per rimuovere il nome "def" da Arraylist in questo modo,

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

Il codice sopra, tramite iteratore, possiamo rimuovere il nome "def" dall'Arraylist e provare a stampare l'array, si vedrà l'output sotto.

Uscita: [abc, ghi, xyz]


Altrimenti, possiamo usare un elenco simultaneo disponibile nel pacchetto simultaneo, in modo da poter eseguire operazioni di rimozione e aggiunta durante l'iterazione. Ad esempio, vedi lo snippet di codice seguente. ArrayList <String> names = new ArrayList <String> (); CopyOnWriteArrayList <String> copyNames = new CopyOnWriteArrayList <String> (names); for (String name: copyNames) {if (name.equals ("def")) {copyNames.remove ("def"); }}
Indra K,

CopyOnWriteArrayList saranno le operazioni più costose.
Indra K,

5

Un'opzione è modificare il removeAmetodo in questo -

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

Ciò significherebbe che doSomething()dovresti essere in grado di passare iteratoral removemetodo. Non è una buona idea.

Puoi farlo in due passaggi: nel primo ciclo quando esegui l'iterazione dell'elenco, invece di rimuovere gli elementi selezionati, contrassegnali come da eliminare . Per questo, puoi semplicemente copiare questi elementi (copia superficiale) in un altro List.

Quindi, una volta terminata la tua iterazione, esegui semplicemente removeAlldal primo elenco tutti gli elementi del secondo elenco.


Eccellente, ho usato lo stesso approccio, anche se faccio due cicli. rende le cose semplici e senza problemi simultanei :)
Pankaj Nimgade,

1
Non vedo che Iterator abbia un metodo remove (a). Remove () non accetta argomenti docs.oracle.com/javase/8/docs/api/java/util/Iterator.html cosa mi sto perdendo?
c0der

5

Ecco un esempio in cui utilizzo un elenco diverso per aggiungere gli oggetti per la rimozione, quindi utilizzo stream.foreach per rimuovere elementi dall'elenco originale:

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

Penso che tu stia facendo un lavoro extra eseguendo due loop, nel peggiore dei casi i loop sarebbero dell'intero elenco. Sarebbe più semplice e meno costoso farlo in un solo ciclo.
Luis Carlos,

Non penso che puoi rimuovere l'oggetto dal primo ciclo, quindi la necessità di un ciclo di rimozione extra, anche il ciclo di rimozione è solo oggetti per la rimozione - forse potresti scrivere un esempio con un solo ciclo, mi piacerebbe vederlo - grazie @ LuisCarlos
serup

Come dici con questo codice, non puoi rimuovere alcun elemento all'interno del ciclo for perché causa l'eccezione java.util.ConcurrentModificationException. Comunque potresti usare un basic per. Qui scrivo un esempio usando parte del tuo codice.
Luis Carlos,

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--); }} È importante i-- perché non vuoi saltare alcun elemento. Inoltre, è possibile utilizzare il metodo removeIf (filtro Predicate <? Super E>) fornito dalla classe ArrayList. Spero che questo aiuto
Luis Carlos,

1
L'eccezione si verifica perché in for-loop è presente come riferimento attivo all'iteratore dell'elenco. Nella norma per, non esiste un riferimento e si dispone di una maggiore flessibilità per modificare i dati. Spero che questo aiuto
Luis Carlos,

3

Invece di usare Per ogni ciclo, usa normale per ciclo. ad esempio, il codice seguente rimuove tutti gli elementi nell'elenco di array senza fornire java.util.ConcurrentModificationException. È possibile modificare la condizione nel ciclo in base al caso d'uso.

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

          e.remove(i);
        }

2

Fai qualcosa di semplice in questo modo:

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

2

Una soluzione Java 8 alternativa che utilizza stream:

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

In Java 7 puoi invece usare Guava:

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

Si noti che l'esempio di Guava risulta in un elenco immutabile che può essere o meno quello che si desidera.


1

È inoltre possibile utilizzare CopyOnWriteArrayList anziché ArrayList. Questo è l'ultimo approccio raccomandato da JDK 1.5 in poi.


1

Nel mio caso, la risposta accettata non funziona, interrompe l'eccezione ma causa alcune incoerenze nella mia lista. La seguente soluzione funziona perfettamente per me.

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

In questo codice, ho aggiunto gli elementi da rimuovere, in un altro elenco e quindi ho usato il list.removeAllmetodo per rimuovere tutti gli elementi richiesti.


0

"Devo clonare prima la lista?"

Questa sarà la soluzione più semplice, rimuoverla dal clone e copiarla nuovamente dopo la rimozione.

Un esempio dal mio gioco 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();
}

2
I downvoter dovrebbero almeno lasciare un commento prima del downvoting.
OneWorld,

2
Non c'è nulla di intrinsecamente sbagliato in questa risposta, forse aspettarsi che stones = (...) clone.clone();sia superfluo. Non stones = clone;farebbe lo stesso?
vikingsteve,

Sono d'accordo, la seconda clonazione non è necessaria. Puoi semplificare ulteriormente ciò ripetendo il clone e rimuovendo gli elementi direttamente da stones. In questo modo non è nemmeno necessaria la clonevariabile: for (Stone stone : (ArrayList<Stone>) stones.clone()) {...
Zsolt Sky,

0

Se il tuo obiettivo è rimuovere tutti gli elementi dall'elenco, puoi scorrere ogni elemento, quindi chiamare:

list.clear()

0

Arrivo tardi lo so, ma rispondo perché penso che questa soluzione sia semplice ed elegante:

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

Tutto questo per l'aggiornamento da una lista all'altra e puoi fare tutto da una sola lista e nell'aggiornamento del metodo puoi controllare entrambe le liste e puoi cancellare o aggiungere elementi tra la lista. Ciò significa che entrambi elencano sempre le stesse dimensioni


0

Utilizzare Iterator anziché Elenco array

Fai convertire un set in iteratore con match di tipo

E passa all'elemento successivo e rimuovi

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

Passare al prossimo è importante qui in quanto dovrebbe prendere l'indice per rimuovere l'elemento.


0

Che dire di

import java.util.Collections;

List<A> abc = Collections.synchronizedList(new ArrayList<>());

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.