Eliminar elementos de un HashSet mientras itera

Por lo tanto, si trato de eliminar elementos de un HashSet de Java mientras realizo la iteración, obtengo una ConcurrentModificationException . ¿Cuál es la mejor manera de eliminar un subconjunto de elementos de un HashSet como en el siguiente ejemplo?

Set set = new HashSet(); for(int i = 0; i < 10; i++) set.add(i); // Throws ConcurrentModificationException for(Integer element : set) if(element % 2 == 0) set.remove(element); 

Aquí hay una solución, pero no creo que sea muy elegante:

 Set set = new HashSet(); Collection removeCandidates = new LinkedList(); 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!

Puede iterar manualmente sobre los elementos del conjunto:

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

A menudo verá este patrón utilizando un ciclo for lugar de un ciclo while:

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

Como han señalado las personas, se prefiere usar un bucle for porque mantiene la variable del iterador ( i en este caso) confinada a un scope menor.

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

El nuevo bucle for es agradable, pero lamentablemente 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 it = set.iterator(); it.hasNext();) { Integer element = it.next(); if (element % 2 == 0) { it.remove(); } } 

también puede refactorizar su solución eliminando el primer ciclo:

 Set set = new HashSet(); Collection removeCandidates = new LinkedList(set); for(Integer element : set) if(element % 2 == 0) removeCandidates.add(element); set.removeAll(removeCandidates); 

Java 8 Collection tiene un buen método llamado removeIf que hace las cosas más fáciles y seguras. De los documentos API:

 default boolean removeIf(Predicate 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(). 

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

Como dijo la madera: “Java 8 Collection tiene un buen método llamado removeIf que hace las cosas más fáciles y seguras”

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

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

Ahora su conjunto contiene solo valores impares.

¿Tiene que ser mientras se itera? Si todo lo que hace es filtrar o seleccionar, le sugiero usar Apache Commons CollectionUtils . Aquí hay algunas herramientas poderosas que hacen que su código sea “más frío”.

Aquí hay una implementación que debería proporcionarle lo que necesita:

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

Si se encuentra usando el mismo tipo de predicado con frecuencia, puede extraerlo en una variable estática para volver a usar … EVEN_NUMBER_PREDICATE 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.

Otra posible solución:

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