2009-07-10 13 views
98

Quindi, se provo a rimuovere gli elementi da un Java HashSet mentre l'iterazione, ho un ConcurrentModificationException. Qual è il modo migliore per rimuovere un sottoinsieme degli elementi da un HashSet come nell'esempio seguente?rimuovere elementi da un HashSet mentre Iterare

Set<Integer> set = new HashSet<Integer>(); 

for(int i = 0; i < 10; i++) 
    set.add(i); 

// Throws ConcurrentModificationException 
for(Integer element : set) 
    if(element % 2 == 0) 
     set.remove(element); 

Ecco una soluzione, ma non credo che sia molto elegante:

Set<Integer> set = new HashSet<Integer>(); 
Collection<Integer> removeCandidates = new LinkedList<Integer>(); 

for(int i = 0; i < 10; i++) 
    set.add(i); 

for(Integer element : set) 
    if(element % 2 == 0) 
     removeCandidates.add(element); 

set.removeAll(removeCandidates); 

Grazie!

risposta

154

È possibile scorrere manualmente sugli elementi del set:

Iterator<Integer> iterator = set.iterator(); 
while (iterator.hasNext()) { 
    Integer element = iterator.next(); 
    if (element % 2 == 0) { 
     iterator.remove(); 
    } 
} 

Vedrete spesso questo schema utilizzando un ciclo for piuttosto che un while ciclo:

for (Iterator<Integer> i = set.iterator(); i.hasNext();) { 
    Integer element = i.next(); 
    if (element % 2 == 0) { 
     i.remove(); 
    } 
} 

Dato che le persone hanno fatto notare , è preferibile utilizzare un ciclo for perché mantiene la variabile iteratore (i in questo caso) confinata in un ambito più piccolo.

+5

Preferisco 'for' a' while', ma ognuno al proprio. –

+1

Uso anche 'for' me stesso. Ho usato 'while' per rendere l'esempio più chiaro. –

+14

I perfer' for' principalmente perché la variabile iteratore è limitata all'ambito del ciclo. –

9

si può anche refactoring la soluzione di rimuovere il primo ciclo:

Set<Integer> set = new HashSet<Integer>(); 
Collection<Integer> removeCandidates = new LinkedList<Integer>(set); 

for(Integer element : set) 
    if(element % 2 == 0) 
     removeCandidates.add(element); 

set.removeAll(removeCandidates); 
+0

ottimo trucco, grazie – Buffalo

+0

Non lo consiglierei perché introduce un accoppiamento temporale nascosto. –

+1

@RomainF. - Cosa intendi per accoppiamento temporale nascosto? Vuoi dire thread sicuro? In secondo luogo, né io lo raccomanderei, ma la soluzione ha il suo pro. Super facile da leggere e quindi mantenibile. – saurabheights

4

Ha bisogno di essere, mentre l'iterazione? Se tutto quello che stai facendo è filtrare o selezionare, suggerirei di usare Apache Commons CollectionUtils. Ci sono alcuni strumenti potenti lì e rende il tuo codice "più fresco".

Ecco un'implementazione che dovrebbe fornire quello che vi serve:

Set<Integer> myIntegerSet = new HashSet<Integer>(); 
// Integers loaded here 
CollectionUtils.filter(myIntegerSet, new Predicate() { 
           public boolean evaluate(Object input) { 
            return (((Integer) input) % 2 == 0); 
           }}); 

Se vi trovate utilizzando lo stesso tipo di predicato spesso si può tirare che fuori in una variabile statica per il riutilizzo ... nome è qualcosa di simile EVEN_NUMBER_PREDICATE. Alcuni potrebbero vedere quel codice e dichiararlo "difficile da leggere" ma sembra più pulito quando si estrae il Predicato in una statica. Quindi è facile vedere che stiamo facendo un CollectionUtils.filter(...) e che sembra più leggibile (per me) di un mucchio di loop su tutta la creazione.

+0

Questa risposta inizia davvero mostrando la sua età ... C'è un modo Java-8 per farlo ora che è probabilmente più pulito. – dustmachine

16

Il motivo si ottiene un ConcurrentModificationException è perché una voce viene rimossa tramite Set.remove() al contrario di Iterator.remove(). Se una voce viene rimossa tramite Set.remove() mentre viene eseguita un'iterazione, verrà visualizzata una ConcurrentModificationException. D'altra parte, la rimozione delle voci tramite Iterator.remove() mentre l'iterazione è supportata in questo caso.

Il nuovo ciclo for è bello, ma sfortunatamente non funziona in questo caso, perché non è possibile utilizzare il riferimento Iterator.

Se è necessario rimuovere una voce durante l'iterazione, è necessario utilizzare il modulo lungo che utilizza direttamente l'Iterator.

for (Iterator<Integer> it = set.iterator(); it.hasNext();) { 
    Integer element = it.next(); 
    if (element % 2 == 0) { 
     it.remove(); 
    } 
} 
+0

@ Il tuo codice non dovrebbe in realtà chiamarlo.next()? – saurabheights

+1

Grazie per quello. Fisso. – sjlee

+0

A che punto è 'elemento' istanziato? –

2

Un'altra soluzione possibile:

for(Object it : set.toArray()) { /* Create a copy */ 
    Integer element = (Integer)it; 
    if(element % 2 == 0) 
     set.remove(element); 
} 

Oppure:

Integer[] copy = new Integer[set.size()]; 
set.toArray(copy); 

for(Integer element : copy) { 
    if(element % 2 == 0) 
     set.remove(element); 
} 
+0

Questo (o la creazione di un 'ArrayList' dal set) è la soluzione migliore se non si rimuovono solo elementi esistenti ma ne si aggiungono di nuovi al set durante il ciclo. –

6

Java 8 Collection ha una bella metodo chiamato removeIf che rende le cose più facili e più sicuro. Dalla documentazione API:

default boolean removeIf(Predicate<? super E> filter) 
Removes all of the elements of this collection that satisfy the given predicate. 
Errors or runtime exceptions thrown during iteration or by the predicate 
are relayed to the caller. 

Nota interessante:

The default implementation traverses all elements of the collection using its iterator(). 
Each matching element is removed using Iterator.remove(). 

Da: https://docs.oracle.com/javase/8/docs/api/java/util/Collection.html#removeIf-java.util.function.Predicate-

+0

Un esempio: 'integerSet.removeIf (intero-> integer.equals (5));' – Jelle

4

come il legno ha detto - "Java 8 Collection ha una bella metodo chiamato removeIf che rende le cose più facili e più sicuro "

Ecco il codice che risolve il problema:

set.removeIf((Integer element) -> { 
    return (element % 2 == 0); 
}); 

Ora il tuo set contiene solo valori dispari.

Problemi correlati