concurrentmodificationexception - recorrer arraylist java foreach
¿Cómo evitar la excepción ConcurrentModificationException al iterar sobre un mapa y cambiar los valores? (7)
Crear un nuevo mapa (mapNew). Luego itere sobre el mapa existente (mapOld), y agregue todas las entradas modificadas y transformadas en mapNew. Una vez completada la iteración, ponga todos los valores de mapNew en mapOld. Esto podría no ser lo suficientemente bueno si la cantidad de datos es grande.
O simplemente use las colecciones de Google : tienen Maps.transformValues()
y Maps.transformEntries()
.
Tengo un mapa que contiene algunas claves (cadenas) y valores (POJOs)
Quiero recorrer este mapa y modificar algunos de los datos en el POJO.
El código actual que he heredado elimina la entrada dada y la vuelve a agregar después de realizar algunos cambios en el POJO.
Esto no funciona bien, ya que no debería modificar un mapa mientras lo hace en iteración (el método está sincronizado, pero sigue apareciendo ConcurrentModificationException)
Mi pregunta es , si necesito recorrer un mapa y cambiar los valores, ¿cuáles son las mejores prácticas / métodos que puedo usar para hacerlo? Para crear un mapa separado y construirlo a medida que avanzo, ¿devolver la copia?
Otro enfoque, algo torturado, es usar AtomicReference como el tipo de valor de su mapa. En su caso, eso significaría declarar su mapa de tipo
Map<String, AtomicReference<POJO>>
Ciertamente, no necesita la naturaleza atómica de la referencia, pero es una forma económica de hacer que las ranuras de valor se puedan volver a unir sin tener que reemplazar toda la Map.Entry
través de Map#put()
.
Aún así, después de leer algunas de las otras respuestas aquí, también recomiendo el uso de Map.Entry#setValue()
, que nunca había necesitado ni notado hasta hoy.
Para poder dar una respuesta adecuada, debes explicar un poco más lo que estás tratando de lograr.
Sin embargo, algunos consejos (posiblemente útiles):
- haga que sus POJOs sean seguros para subprocesos y haga actualizaciones de datos en POJOs directamente. Entonces no es necesario manipular el mapa.
- utilizar ConcurrentHashMap
- siga usando el HashMap simple , pero cree un nuevo mapa en cada modificación y cambie los mapas entre bambalinas (sincronizando la operación del interruptor o usando la AtomicReference )
El mejor enfoque depende en gran medida de su aplicación, es difícil darle alguna "mejor práctica". Como siempre, haga su propio punto de referencia con datos realistas.
Para tales fines, debe utilizar las vistas de colección que expone un mapa:
- keySet() te permite iterar sobre las teclas. Eso no te ayudará, ya que las llaves suelen ser inmutables.
- values() es lo que necesita si solo desea acceder a los valores del mapa. Si son objetos mutables, puede cambiarlos directamente, sin necesidad de volver a colocarlos en el mapa.
- entrySet() la versión más potente, le permite cambiar la clave y / o el valor de una entrada.
Ejemplo: convertir los valores de todas las claves que contienen un upperscore a mayúsculas
for(Map.Entry<String, String> entry:map.entrySet()){
if(entry.getKey().contains("_"))
entry.setValue(entry.getValue().toUpperCase());
}
En realidad, si solo desea editar los objetos de valor, hágalo usando la colección de valores. Supongo que su mapa es de tipo <String, Object>
:
for(Object o: map.values()){
if(o instanceof MyBean){
((Mybean)o).doStuff();
}
}
Si se itera sobre un Map
y se agregan entradas al mismo tiempo, se generará una ConcurrentModificationException
para la mayoría de las clases de Map
. Y para las clases de Map
que no lo hacen (por ejemplo, ConcurrentHashMap
) no hay garantía de que una iteración visite todas las entradas.
Dependiendo de lo que está haciendo exactamente, es posible que pueda hacer lo siguiente mientras está iterando:
- use el método
Iterator.remove()
para eliminar la entrada actual, o - use el método
Map.Entry.setValue()
para modificar el valor de la entrada actual.
Para otros tipos de cambio, es posible que necesite:
- crear un nuevo
Map
partir de las entradas en elMap
actual, o - cree una estructura de datos separada que contenga los cambios que se realizarán y, a continuación, se aplicará al
Map
.
Y, finalmente, las bibliotecas Google Collections y Apache Commons Collections tienen clases de utilidad para "transformar" los mapas.
Trate de usar ConcurrentHashMap .
Desde JavaDoc,
Una tabla hash que admite la concurrencia total de las recuperaciones y la concurrencia esperada ajustable para las actualizaciones.
Para que ocurra ConcurrentModificationException, generalmente:
generalmente no está permitido que un hilo modifique una Colección mientras que otro hilo está iterando sobre él.
Dos opciones:
Opción 1
El código actual que he heredado elimina la entrada dada y la vuelve a agregar después de realizar algunos cambios en el POJO.
¿Estás cambiando la referencia al POJO? Por ejemplo, ¿la entrada apunta a algo completamente distinto? Porque si no es así, no hay necesidad de eliminarlo del mapa, simplemente puede cambiarlo.
opcion 2
Si realmente necesita cambiar la referencia al POJO (por ejemplo, el valor de la entrada), aún puede hacerlo en su lugar iterando sobre las instancias de entrySet()
desde entrySet()
. Puede usar setValue
en la entrada, que no modifica lo que está iterando.
Ejemplo:
Map<String,String> map;
Map.Entry<String,String> entry;
Iterator<Map.Entry<String,String>> it;
// Create the map
map = new HashMap<String,String>();
map.put("one", "uno");
map.put("two", "due");
map.put("three", "tre");
// Iterate through the entries, changing one of them
it = map.entrySet().iterator();
while (it.hasNext())
{
entry = it.next();
System.out.println("Visiting " + entry.getKey());
if (entry.getKey().equals("two"))
{
System.out.println("Modifying it");
entry.setValue("DUE");
}
}
// Show the result
it = map.entrySet().iterator();
while (it.hasNext())
{
entry = it.next();
System.out.println(entry.getKey() + "=" + entry.getValue());
}
La salida (en ningún orden en particular) es:
Visitando dos
Modificandolo
Visitando uno
Visitando tres
dos = DUE
uno = uno
tres = tre
... sin ninguna excepción de modificación. Probablemente querrá sincronizar esto en caso de que otra cosa también esté mirando / mucking con esa entrada.