Perché non sto ottenendo un java.util.ConcurrentModificationException in questo esempio?


176

Nota: sono a conoscenza del Iterator#remove()metodo.

Nel seguente esempio di codice, non capisco il motivo per cui l' List.removenel mainmetodo genera ConcurrentModificationException, ma non nel removemetodo.

public class RemoveListElementDemo {    
    private static final List<Integer> integerList;

    static {
        integerList = new ArrayList<Integer>();
        integerList.add(1);
        integerList.add(2);
        integerList.add(3);
    }

    public static void remove(Integer toRemove) {
        for(Integer integer : integerList) {
            if(integer.equals(toRemove)) {                
                integerList.remove(integer);
            }
        }
    }

    public static void main(String... args) {                
        remove(Integer.valueOf(2));

        Integer toRemove = Integer.valueOf(3);
        for(Integer integer : integerList) {
            if(integer.equals(toRemove)) {                
                integerList.remove(integer);
            }
        }
    }
}

3
L'unico modo sicuro per rimuovere un elemento da un elenco mentre si scorre su tale elenco è utilizzare Iterator#remove(). Perché lo fai in questo modo?
Matt Ball,

@MattBall: stavo solo cercando di capire quale potesse essere la ragione qui. Perché, è lo stesso "potenziato per loop" in entrambi i metodi ma uno lancia ConcurrentModificationExceptione l'altro no.
Bhesh Gurung,

C'è una differenza nell'elemento che rimuovi. Nel metodo rimuovi "l'elemento centrale". In generale rimuovi l'ultimo. Se si scambiano i numeri si ottiene l'eccezione nel metodo. Non sono ancora sicuro del perché.
Ben van Gompel,

Ho avuto un problema simile, quando il mio ciclo ripeteva anche una posizione che non esisteva dopo aver rimosso un elemento nel ciclo. Ho semplicemente risolto questo aggiungendo a return;nel loop.
Frank17,

su java8 Android, la rimozione di un elemento diverso dall'ultimo invocherebbe ConcurrentModificationException. quindi per il tuo caso, rimuovere la funzione otterrebbe un'eccezione che è opposta a come hai osservato prima.
Gonglong,

Risposte:


262

Ecco perché: Come si dice nel Javadoc:

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.

Questo controllo viene eseguito nel next()metodo dell'iteratore (come puoi vedere dallo stacktrace). Ma raggiungeremo il next()metodo solo se hasNext()consegnato vero, che è ciò che viene chiamato da ciascuno per verificare se il limite è soddisfatto. Nel tuo metodo di rimozione, quando hasNext()controlla se deve restituire un altro elemento, vedrà che ha restituito due elementi e ora dopo che un elemento è stato rimosso l'elenco contiene solo due elementi. Quindi tutto è peachy e abbiamo finito con l'iterazione. Il controllo per le modifiche simultanee non si verifica, poiché questo viene fatto nel next()metodo che non viene mai chiamato.

Quindi arriviamo al secondo ciclo. Dopo aver rimosso il secondo numero, il metodo hasNext controllerà nuovamente se è possibile restituire più valori. Ha già restituito due valori, ma l'elenco ora ne contiene solo uno. Ma il codice qui è:

public boolean hasNext() {
        return cursor != size();
}

1! = 2, quindi continuiamo con il next()metodo, che ora si rende conto che qualcuno ha rovinato l'elenco e genera l'eccezione.

Spero che chiarisca la tua domanda.

Sommario

List.remove()non verrà lanciato ConcurrentModificationExceptionquando rimuove il secondo ultimo elemento dall'elenco.


5
@pushy: solo una risposta che sembra rispondere a ciò che la domanda sta effettivamente ponendo, e la spiegazione è buona. Accetto questa risposta e anche +1. Grazie.
Bhesh Gurung,

42

Un modo per gestirlo per rimuovere qualcosa da una copia di una Collection(non Collezione stessa), se applicabile. Clonela raccolta originale per fare una copia tramite a Constructor.

Questa eccezione può essere generata da metodi che hanno rilevato modifiche simultanee di un oggetto quando tale modifica non è consentita.

Per il tuo caso specifico, prima di tutto, non credo finalsia un modo di procedere considerando che intendi modificare l'elenco delle dichiarazioni passate

private static final List<Integer> integerList;

Considera anche di modificare una copia anziché l'elenco originale.

List<Integer> copy = new ArrayList<Integer>(integerList);

for(Integer integer : integerList) {
    if(integer.equals(remove)) {                
        copy.remove(integer);
    }
}

14

Il metodo forward / iteratore non funziona durante la rimozione di elementi. Puoi rimuovere l'elemento senza errori, ma otterrai un errore di runtime quando provi ad accedere agli elementi rimossi. Non puoi usare l'iteratore perché, come mostra pushy, causerà una ConcurrentModificationException, quindi usa invece un normale ciclo per, ma fai un passo indietro.

List<Integer> integerList;
integerList = new ArrayList<Integer>();
integerList.add(1);
integerList.add(2);
integerList.add(3);

int size= integerList.size();

//Item to remove
Integer remove = Integer.valueOf(3);

Una soluzione:

Attraversare l'array in ordine inverso se si desidera rimuovere un elemento elenco. Semplicemente andando indietro nell'elenco, si evita di visitare un elemento che è stato rimosso, rimuovendo l'eccezione.

//To remove items from the list, start from the end and go backwards through the arrayList
//This way if we remove one from the beginning as we go through, then we will avoid getting a runtime error
//for java.lang.IndexOutOfBoundsException or java.util.ConcurrentModificationException as when we used the iterator
for (int i=size-1; i> -1; i--) {
    if (integerList.get(i).equals(remove) ) {
        integerList.remove(i);
    }
}

idea brillante !
dobrivoje,

7

Questo frammento genererà sempre un'eccezione ConcurrentModificationException.

La regola è "Non è possibile modificare (aggiungere o rimuovere elementi dall'elenco) mentre si scorre su di esso utilizzando un Iteratore (che si verifica quando si utilizza un ciclo for-each)".

JavaDocs:

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.

Quindi, se si desidera modificare l'elenco (o qualsiasi raccolta in generale), utilizzare iteratore, perché è a conoscenza delle modifiche e quindi quelle verranno gestite correttamente.

Spero che questo ti aiuti.


3
L'OP afferma chiaramente che uno dei loop NON genera un'eccezione e l'interrogante era il motivo per cui ciò è accaduto.
madth3,

cosa intendi con "interrogante"?
Bhushan,

4

Ho avuto lo stesso problema ma nel caso in cui stavo aggiungendo en element nell'elenco iterato. L'ho fatto in questo modo

public static void remove(Integer remove) {
    for(int i=0; i<integerList.size(); i++) {
        //here is maybe fine to deal with integerList.get(i)==null
        if(integerList.get(i).equals(remove)) {                
            integerList.remove(i);
        }
    }
}

Ora tutto va bene perché non si crea alcun iteratore sull'elenco, si scorre su di esso "manualmente". E condizionii < integerList.size() non ti ingannerà mai perché quando rimuovi / aggiungi qualcosa nelle dimensioni dell'elenco del decremento / incremento dell'elenco.

Spero che sia d'aiuto, per me quella era una soluzione.


Questo non è vero ! Prova: esegui questo frammento per vedere il risultato: vuoto statico pubblico principale (String ... args) {List <String> listOfBooks = new ArrayList <> (); listOfBooks.add ("Codice completo"); listOfBooks.add ("Codice 22"); listOfBooks.add ("22 Effective"); listOfBooks.add ("Netbeans 33"); System.err.println ("Prima di eliminare:" + listOfBooks); for (int index = 0; index <listOfBooks.size (); index ++) {if (listOfBooks.get (index) .contains ("22")) {listOfBooks.remove (index); }} System.err.println ("Dopo l'eliminazione:" + listOfBooks); }
dobrivoje,

1

Se usi raccolte copy-on-write funzionerà; tuttavia quando usi list.iterator (), l'Iteratore restituito farà sempre riferimento alla raccolta di elementi come era quando (come sotto) è stato chiamato list.iterator (), anche se un altro thread modifica la raccolta. Qualsiasi metodo di mutazione chiamato su un Iteratore o ListIterator basato su copia su scrittura (come aggiungere, impostare o rimuovere) genererà un UnsupportedOperationException.

import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;

public class RemoveListElementDemo {    
    private static final List<Integer> integerList;

    static {
        integerList = new CopyOnWriteArrayList<>();
        integerList.add(1);
        integerList.add(2);
        integerList.add(3);
    }

    public static void remove(Integer remove) {
        for(Integer integer : integerList) {
            if(integer.equals(remove)) {                
                integerList.remove(integer);
            }
        }
    }

    public static void main(String... args) {                
        remove(Integer.valueOf(2));

        Integer remove = Integer.valueOf(3);
        for(Integer integer : integerList) {
            if(integer.equals(remove)) {                
                integerList.remove(integer);
            }
        }
    }
}

0

Funziona bene su Java 1.6

~% javac RemoveListElementDemo.java
~% java RemoveListElementDemo
~% cat RemoveListElementDemo.java

import java.util.*;
public class RemoveListElementDemo {    
    private static final List<Integer> integerList;

    static {
        integerList = new ArrayList<Integer>();
        integerList.add(1);
        integerList.add(2);
        integerList.add(3);
    }

    public static void remove(Integer remove) {
        for(Integer integer : integerList) {
            if(integer.equals(remove)) {                
                integerList.remove(integer);
            }
        }
    }

    public static void main(String... args) {                
        remove(Integer.valueOf(2));

        Integer remove = Integer.valueOf(3);
        for(Integer integer : integerList) {
            if(integer.equals(remove)) {                
                integerList.remove(integer);
            }
        }
    }
}

~%


Mi dispiace per l'errore di battitura Questo "funziona" bene su Java 1.6
battosai,

Hmm ... Forse hai un'implementazione diversa. Ma secondo le specifiche dovrebbe farlo, IMO. Guarda la risposta di @ Pushy.
Bhesh Gurung,

sfortunatamente, id non funziona su java 1.8
dobrivoje,

0

Nel mio caso l'ho fatto in questo modo:

int cursor = 0;
do {
    if (integer.equals(remove))
        integerList.remove(cursor);
    else cursor++;
} while (cursor != integerList.size());

0

Cambia Iteratore for eachinfor loop per risolvere.

E il motivo è:

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.

- Documenti Java referenziati.


-1

Controlla il tuo codice man ....

Nel metodo principale stai cercando di rimuovere il 4 ° elemento che non c'è e quindi l'errore. Nel metodo remove () stai tentando di rimuovere il 3 ° elemento che è lì e quindi nessun errore.


Ti sbagli: i numeri 2e 3non sono indici per l'elenco, ma elementi. Entrambe le logiche di rimozione controllano equalsgli elementi della lista, non l'indice degli elementi. Inoltre, se è stato correlato indice, sarebbe IndexOutOfBoundsException, non è ConcurrentModificationException.
Malte Hartwig,
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.