java - ¿Por qué List.addAll de una sublista invertida de la lista causa una ConcurrentModificationException
arraylist collections (4)
Esta pregunta ya tiene una respuesta aquí:
He estado tratando de tomar una
sublista
de una lista, invertirla y colocar la lista
reversed
nuevamente en la posición inicial.
Por ejemplo, supongamos que tenemos la lista
[1, 2, 3, 4, 5, 6]
, luego, al revertir del índice 2 al índice 4, obtendría
[1, 2, 5, 4, 3, 6]
.
He escrito un código para esto, sin embargo, proporciona una
ConcurrentModificationException
cada vez (a menos que startIndex == endIndex).
A continuación se proporciona un ejemplo reproducible mínimo:
int startIndex = 2;
int endIndex = 4;
List<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
list.add(4);
list.add(5);
list.add(6);
List<Integer> toReverse = list.subList(startIndex, endIndex+1);
Collections.reverse(toReverse);
list.removeAll(toReverse);
list.addAll(startIndex, toReverse);
Excepción en el hilo "main" java.util.ConcurrentModificationException
en java.util.ArrayList $ SubList.checkForComodification (Fuente desconocida)
en java.util.ArrayList $ SubList.size (Fuente desconocida) en
java.util.AbstractCollection.toArray (fuente desconocida) en
java.util.ArrayList.addAll (fuente desconocida) en
test.ConcurrentExample.main (ConcurrentExample.java:64)
La línea real a la que se refiere el error es
list.addAll(startIndex, toReverse);
.
No estoy seguro de cuál es el problema, ya que nada parece estar cambiando mientras se repite. Si alguien pudiera explicar por qué sucede esto y / o cómo solucionarlo, sería muy apreciado.
A partir de sugerencias de helospark y Nir Levy , use omitir y limitar en Stream
List<Integer> toReverse = list.stream() //
.skip(startIndex) //
.limit(endIndex + 1) //
.collect(Collectors.toList());
El problema se encuentra aquí en
ArrayList#checkForComodification
private void checkForComodification() {
if (ArrayList.this.modCount != this.modCount)
throw new ConcurrentModificationException();
}
}
Sin embargo, en este caso particular, no necesita volver a agregar manualmente la sublista invertida, ya que la reversión se realiza en la lista original . Así que todo lo que necesitas es dejar caer
list.removeAll(...);
list.addAll(...);
y deja solo este código:
List<Integer> toReverse = list.subList(startIndex, endIndex+1);
Collections.reverse(toReverse);
List.subList devuelve una vista en vivo de la lista entre los elementos especificados, no una copia de esos elementos (ver
documentation
), por lo tanto, agregar a la lista original también modificará la sublista, lo que conducirá a
ConcurrentModificationException
(ya que lo que se agrega y a lo que agrega también se modifican al mismo tiempo).
list.subList(startIndex, endIndex+1)
Puede arreglar un código copiando la lista, como
List<Integer> toReverse = new ArrayList<>(list.subList(startIndex, endIndex+1));
de la documentación de ArrayList.subList :
La lista devuelta está respaldada por esta lista, por lo que los cambios no estructurales en la lista devuelta se reflejan en esta lista, y viceversa.
Entonces, cuando intenta agregar elementos en el índice de la ''vista'' de la sublista, se crea una modificación concurrente.