Chiamata remove in foreach loop in Java [duplicate]


Questa domanda ha già una risposta qui:

In Java, è legale chiamare remove su una raccolta quando si esegue l'iterazione attraverso la raccolta utilizzando un ciclo foreach? Ad esempio:

List<String> names = ....
for (String name : names) {
   // Do something
   names.remove(name).
}

Come addendum, è legale rimuovere gli elementi che non sono stati iterati sopra ancora? Ad esempio,

//Assume that the names list as duplicate entries
List<String> names = ....
for (String name : names) {
    // Do something
    while (names.remove(name));
}
Author: James McMahon, 2009-07-29

11 answers

Per rimuovere in modo sicuro da una raccolta durante l'iterazione su di essa è necessario utilizzare un iteratore.

Per esempio:

List<String> names = ....
Iterator<String> i = names.iterator();
while (i.hasNext()) {
   String s = i.next(); // must be called before you can call i.remove()
   // Do something
   i.remove();
}

Dalla documentazione Java:

Gli iteratori restituiti dall'iteratore e dal listiteratore di questa classe i metodi sono fail-fast: se l'elenco è strutturalmente modificato in qualsiasi tempo dopo la creazione dell'iteratore, in qualsiasi modo tranne attraverso il i propri metodi di rimozione o aggiunta dell'iteratore, l'iteratore lancerà un ConcurrentModificationException. Quindi, di fronte alla concorrenza modifica, l'iteratore fallisce rapidamente e in modo pulito, piuttosto che rischiando un comportamento arbitrario e non deterministico in un tempo indeterminato in futuro.

Forse ciò che non è chiaro a molti novizi è il fatto che iterare su una lista usando i costrutti for/foreach crea implicitamente un iteratore che è necessariamente inaccessibile. Questa informazione può essere trovata qui

 792
Author: Mark, 2014-12-02 11:11:03

Non vuoi farlo. Può causare un comportamento indefinito a seconda della raccolta. Si desidera utilizzare direttamente un iteratore . Sebbene per ogni costrutto sia zucchero sintattico e stia davvero usando un iteratore, lo nasconde dal tuo codice in modo da non poterlo accedere alla chiamata Iterator.remove.

Il comportamento di un iteratore è non specificato se il sottostante la raccolta viene modificata mentre il l'iterazione è in corso in qualsiasi modo oltre a chiamare questo metodo.

Invece scrivi il tuo codice:

List<String> names = ....
Iterator<String> it = names.iterator();
while (it.hasNext()) {

    String name = it.next();
    // Do something
    it.remove();
}

Si noti che il codice chiama Iterator.remove, non List.remove.

Addendum:

Anche se stai rimuovendo un elemento che non è stato ancora iterato, non vuoi ancora modificare la raccolta e quindi utilizzare Iterator. Potrebbe modificare la raccolta in un modo sorprendente e influenzare le operazioni future sul Iterator.

 152
Author: Jared Oberhaus, 2009-07-28 20:48:37

Il design java di "enhanced for loop" consisteva nel non esporre l'iteratore al codice, ma l'unico modo per rimuovere in modo sicuro un elemento è accedere all'iteratore. Quindi in questo caso devi farlo vecchia scuola:

 for(Iterator<String> i = names.iterator(); i.hasNext();) {
       String name = i.next();
       //Do Something
       i.remove();
 }

Se nel codice reale il ciclo for avanzato vale davvero la pena, è possibile aggiungere gli elementi a una raccolta temporanea e chiamare removeAll nell'elenco dopo il ciclo.

EDIT (re addendum): No, cambiando l'elenco in qualsiasi modo al di fuori dell'iteratore.remove () metodo mentre l'iterazione causerà problemi. L'unico modo per aggirare questo è usare un CopyOnWriteArrayList, ma che è davvero destinato a problemi di concorrenza.

Il modo più economico (in termini di righe di codice) per rimuovere i duplicati è scaricare l'elenco in un LinkedHashSet (e poi di nuovo in un elenco se necessario). Questo mantiene l'ordine di inserimento durante la rimozione dei duplicati.

 55
Author: Yishai, 2014-07-18 13:31:40
for (String name : new ArrayList<String>(names)) {
    // Do something
    names.remove(nameToRemove);
}

Clonare l'elenco names e scorrere il clone mentre si rimuove dall'elenco originale. Un po ' più pulito della risposta migliore.

 44
Author: ktamlyn, 2015-11-22 03:37:30

Non sapevo degli iteratori, tuttavia ecco cosa stavo facendo fino ad oggi per rimuovere elementi da una lista all'interno di un ciclo:

List<String> names = .... 
for (i=names.size()-1;i>=0;i--) {    
    // Do something    
    names.remove(i);
} 

Funziona sempre e potrebbe essere utilizzato in altri linguaggi o strutture che non supportano gli iteratori.

 24
Author: Serafeim, 2013-02-27 14:53:42

Sì è possibile utilizzare il ciclo for-each, Per fare ciò è necessario mantenere un elenco separato per contenere la rimozione di elementi e quindi rimuovere tale elenco dall'elenco dei nomi utilizzando il metodo removeAll(),

List<String> names = ....

// introduce a separate list to hold removing items
List<String> toRemove= new ArrayList<String>();

for (String name : names) {
   // Do something: perform conditional checks
   toRemove.add(name);
}    
names.removeAll(toRemove);

// now names list holds expected values
 20
Author: Chathuranga Withana, 2010-12-11 13:00:54

Coloro che dicono che non è possibile rimuovere in modo sicuro un elemento da una raccolta tranne che attraverso l'Iteratore non sono del tutto corretti, è possibile farlo in modo sicuro utilizzando una delle raccolte simultanee come ConcurrentHashMap.

 3
Author: sanity, 2009-07-28 23:25:51

Assicurati che questo non sia odore di codice. È possibile invertire la logica ed essere "inclusivi" piuttosto che "esclusivi"?

List<String> names = ....
List<String> reducedNames = ....
for (String name : names) {
   // Do something
   if (conditionToIncludeMet)
       reducedNames.add(name);
}
return reducedNames;

La situazione che mi ha portato a questa pagina ha coinvolto il vecchio codice che scorreva attraverso una lista usando indecies per rimuovere gli elementi dalla lista. Volevo rifattorizzarlo per usare lo stile foreach.

Ha eseguito il loop di un intero elenco di elementi per verificare a quali utenti era consentito accedere e ha rimosso quelli che non avevano elenco.

List<Service> services = ...
for (int i=0; i<services.size(); i++) {
    if (!isServicePermitted(user, services.get(i)))
         services.remove(i);
}

Per invertire questo e non utilizzare il remove:

List<Service> services = ...
List<Service> permittedServices = ...
for (Service service:services) {
    if (isServicePermitted(user, service))
         permittedServices.add(service);
}
return permittedServices;

Quando sarebbe preferibile "rimuovi"? Una considerazione è se gien una grande lista o costoso "add", combinato con solo pochi rimosso rispetto alla dimensione della lista. Potrebbe essere più efficiente fare solo alcune rimuove piuttosto che un gran numero di aggiunge. Ma nel mio caso la situazione non meritava una tale ottimizzazione.

 3
Author: bmcdonald, 2011-11-14 16:57:57
  1. Prova questo 2. e cambia la condizione in "INVERNO" e ti chiederai:
public static void main(String[] args) {
  Season.add("Frühling");
  Season.add("Sommer");
  Season.add("Herbst");
  Season.add("WINTER");
  for (String s : Season) {
   if(!s.equals("Sommer")) {
    System.out.println(s);
    continue;
   }
   Season.remove("Frühling");
  }
 }
 1
Author: Carsten, 2011-08-30 15:42:18

È meglio usare un iteratore quando si desidera rimuovere l'elemento da un elenco

Perché il codice sorgente di remove è

if (numMoved > 0)
    System.arraycopy(elementData, index+1, elementData, index,
             numMoved);
elementData[--size] = null;

Quindi ,se rimuovi un elemento dall'elenco, l'elenco verrà ristrutturato ,l'indice dell'altro elemento verrà modificato, questo può risultare qualcosa che vuoi accadere.

 1
Author: song, 2012-02-09 08:51:44

Utilizzare

.remove () di Interator o

Utilizzare

Elenco di CopyOnWriteArrayList

 -5
Author: ThmHarsh, 2014-10-10 05:41:46