Chiamata rimuovi dal ciclo foreach in Java

In Java, è legale richiamare la rimozione su una raccolta durante l’iterazione attraverso la raccolta utilizzando un ciclo foreach? Per esempio:

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

Come addendum, è legale rimuovere gli oggetti che non sono stati ancora iterati? Per esempio,

 //Assume that the names list as duplicate entries List names = .... for (String name : names) { // Do something while (names.remove(name)); } 

Per rimuovere in sicurezza da una raccolta mentre si sta iterando su di essa, è necessario utilizzare un Iterator.

Per esempio:

 List names = .... Iterator 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’iter iteratore di questa class e i metodi listIterator sono veloci: se la lista viene modificata strutturalmente in qualsiasi momento dopo la creazione dell’iteratore, in qualsiasi modo tranne che attraverso i metodi di rimozione o aggiunta dell’iteratore, l’iteratore genererà una ConcurrentModificationException. Quindi, di fronte a modifiche simultanee, l’iteratore fallisce rapidamente e in modo pulito, piuttosto che rischiare di comportarsi in modo arbitrario e non deterministico in un tempo indeterminato nel futuro.

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

Tu non vuoi farlo. Può causare un comportamento indefinito a seconda della collezione. Vuoi usare un Iterator direttamente. Sebbene per ogni costrutto sia lo zucchero sintattico e stia davvero usando un iteratore, lo nasconde dal codice in modo da non poterlo accedere per chiamare Iterator.remove .

Il comportamento di un iteratore non è specificato se la raccolta sottostante viene modificata mentre l’iterazione è in corso in un modo diverso dal chiamare questo metodo.

Invece scrivi il tuo codice:

 List names = .... Iterator it = names.iterator(); while (it.hasNext()) { String name = it.next(); // Do something it.remove(); } 

Notare 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 collezione in un modo che è sorprendente e influenza le future operazioni su Iterator .

Il design java del “enhanced for loop” era di non esporre l’iteratore al codice, ma l’unico modo per rimuovere un object in sicurezza è accedere all’iteratore. Quindi in questo caso devi farlo vecchia scuola:

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

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

EDIT (re aggiunta): No, cambiando la lista in qualsiasi modo al di fuori del metodo iterator.remove () durante l’iterazione causerà problemi. L’unico modo per aggirare questo è usare CopyOnWriteArrayList, ma questo è veramente inteso per problemi di concorrenza.

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

 for (String name : new ArrayList(names)) { // Do something names.remove(nameToRemove); } 

Cloni i names liste e iterate attraverso il clone mentre rimuovi dall’elenco originale. Un po ‘più pulito della risposta migliore.

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

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

Funziona sempre e potrebbe essere utilizzato in altre lingue o strutture che non supportano gli iteratori.

Sì, puoi usare il ciclo for-each, Per farlo devi mantenere un elenco separato per contenere la rimozione di elementi e quindi rimuovere removeAll() dalla lista dei nomi usando il metodo removeAll() ,

 List names = .... // introduce a separate list to hold removing items List toRemove= new ArrayList(); for (String name : names) { // Do something: perform conditional checks toRemove.add(name); } names.removeAll(toRemove); // now names list holds expected values 

Chi dice che non è ansible rimuovere in sicurezza un object da una raccolta, ad eccezione di Iterator, non è del tutto corretto, è ansible farlo in sicurezza utilizzando una delle raccolte simultanee come ConcurrentHashMap.

Assicurati che questo non sia odore di codice. È ansible invertire la logica ed essere “inclusivi” piuttosto che “esclusivi”?

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

La situazione che mi ha portato a questa pagina riguardava il vecchio codice che collegava un elenco usando le indecie per rimuovere elementi dall’elenco. Volevo refactoring per utilizzare lo stile foreach.

Ha fatto il giro di un intero elenco di elementi per verificare quali utenti l’utente aveva il permesso di accedere e ha rimosso quelli che non avevano il permesso dalla lista.

 List services = ... for (int i=0; i 

Per invertire questo e non utilizzare la rimozione:

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

Quando si dovrebbe "rimuovere" essere preferito? Una considerazione è se gien un elenco di grandi dimensioni o costoso "aggiungi", combinato con solo pochi rimossi rispetto alla dimensione dell'elenco. Potrebbe essere più efficiente fare solo alcuni rimossi piuttosto che un gran numero di aggiunte. Ma nel mio caso la situazione non meritava una tale ottimizzazione.

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

È meglio utilizzare un Iterator quando si desidera rimuovere un elemento da un elenco

perché il codice sorgente di rimozione è

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

quindi, se rimuovi un elemento dalla lista, la lista sarà ristrutturata, l’indice dell’altro elemento sarà cambiato, questo può risultare qualcosa che vuoi che succeda.

Uso

.remove () di Interator o

Uso

CopyOnWriteArrayList