2009-07-10 49 views
98

Por lo tanto, si intento para eliminar los elementos de un HashSet Java mientras que la iteración, consigo un ConcurrentModificationException. ¿Cuál es la mejor manera de eliminar un subconjunto de elementos de un HashSet como en el siguiente ejemplo?eliminar elementos de un HashSet al iterar

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

Aquí es una solución, pero no creo que es muy 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); 

Gracias!

Respuesta

154

Se puede recorrer de forma manual a través de los elementos del conjunto:

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

A menudo se verá este patrón usando un bucle for en lugar de un while bucle:

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

Como ya se ha señalado , se prefiere utilizar un bucle for porque mantiene la variable del iterador (i en este caso) limitada a un ámbito menor.

+5

Prefiero 'for' a' while', pero cada uno por su cuenta. –

+1

También uso 'for'. Usé 'while' para, con suerte, aclarar el ejemplo. –

+14

Opino 'for' sobre todo porque la variable del iterador está entonces limitada al alcance del ciclo. –

9

que también puede perfeccionar por su solución de retirar el primer bucle:

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

gran truco, gracias – Buffalo

+0

No lo recomendaría ya que presenta un acoplamiento temporal oculto. –

+1

@RomainF. - ¿Qué quieres decir con acoplamiento temporal oculto? ¿Quieres decir hilo seguro? En segundo lugar, tampoco recomendaría esto, pero la solución tiene su ventaja. Súper fácil de leer y, por tanto, fácil de mantener. – saurabheights

4

¿Tiene que ser, mientras que la iteración? Si todo lo que hace es filtrar o seleccionar, le sugiero que use Apache Commons CollectionUtils. Aquí hay algunas herramientas poderosas que hacen que su código sea "más frío".

Aquí es una implementación que debe proporcionar lo que necesita:

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

Si usted se encuentra usando el mismo tipo de predicado con frecuencia se puede tirar de que fuera en una variable estática para su reutilización ... nombre algo así como EVEN_NUMBER_PREDICATE. Es posible que algunos vean ese código y lo declaren "difícil de leer", pero se ve más claro cuando extrae el Predicado en una estática. Entonces es fácil ver que estamos haciendo un CollectionUtils.filter(...) y que parece más legible (para mí) que un montón de bucles en toda la creación.

+0

Esta respuesta realmente comienza a mostrar su edad ... Hay una forma Java-8 de hacer esto ahora que es sin dudas más limpia. – dustmachine

16

La razón se obtiene una ConcurrentModificationException se debe a que se elimina una entrada a través de Set.remove() en contraposición a Iterator.remove(). Si se elimina una entrada a través de Set.remove() mientras se realiza una iteración, obtendrá una ConcurrentModificationException. Por otro lado, la eliminación de entradas a través de Iterator.remove(), mientras que la iteración es compatible en este caso.

El nuevo bucle for es agradable, pero desafortunadamente no funciona en este caso, porque no puede usar la referencia de Iterator.

Si necesita eliminar una entrada durante la iteración, necesita usar la forma larga que usa directamente el iterador.

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

@ ¿No debería su código realmente llamarlo.next()? – saurabheights

+1

Gracias por eso. Fijo. – sjlee

+0

¿En qué punto se 'instancia' el 'elemento'? –

2

Otra solución posible:

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

O:

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

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

Eso (o la creación de un 'ArrayList' fuera del conjunto) es la mejor solución si no solo elimina elementos existentes sino que también agrega elementos nuevos al conjunto durante el ciclo. –

6

Java 8 Collection tiene un buen método llamado removeIf que hace las cosas más fácil y más seguro. A partir de los documentos de la 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 interesante:

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

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

+0

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

4

como la madera dicho - "Java Colección 8 tiene una llamada removeIf buen método que hace las cosas más fáciles y seguras "

Aquí está el código que resuelve su problema:

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

Ahora su conjunto solo contiene valores impares.

Cuestiones relacionadas