Te lleva a través de los cinco grandes hoyos de List de una vez, ¡realmente hay hoyos en todas partes! | Original

Te lleva a través de los cinco grandes hoyos de List de una vez, ¡realmente hay hoyos en todas partes! | Original

Abajo, hermanito negro, negro pequeño, once y media

Te lleva a través de los cinco grandes hoyos de List de una vez, ¡realmente hay hoyos en todas partes!  | Original

Se puede decir que List es una de las clases de colección que usamos a menudo, y casi todos los códigos comerciales son inseparables de List. Dado que se usa todos los días, puede pisar estos pozos de lista comunes.
Hoy resumiremos dónde están estos pozos comunes y lo aprovecharemos para evitar que los estudiantes posteriores pisen los pozos.
Los puntos de conocimiento de diseño de este artículo son los siguientes:
Te lleva a través de los cinco grandes hoyos de List de una vez, ¡realmente hay hoyos en todas partes!  | Original
Lista

ArrayList ¿Es este Li Kui o Li Gui?

En mi pasantía anterior, escribí un código tan simple para convertir una matriz en una colección List a través de Arrays # asList.
Te lleva a través de los cinco grandes hoyos de List de una vez, ¡realmente hay hoyos en todas partes!  | Original
Este código parece no tener problemas en la superficie, y la compilación puede pasar, pero cuando se ejecuta la prueba real, se lanzará una UnsupportedOperationException en la línea 4.
Al principio, estaba perplejo. El retorno de Arrays # asList es claramente una ArrayList. ¿Por qué se reportaría un error al agregar un elemento? ¿Puedes agregar nuevos elementos bien en el futuro?
Te lleva a través de los cinco grandes hoyos de List de una vez, ¡realmente hay hoyos en todas partes!  | Original
Finalmente, a través de Debug, descubrí que ArrayList devuelto por Arrays # asList es en realidad un fantasma, solo una clase interna de Arrays, no un java.util.ArrayList real.
Te lleva a través de los cinco grandes hoyos de List de una vez, ¡realmente hay hoyos en todas partes!  | Original
A través de IDEA, los dos diagramas de clases se generan de la siguiente manera:
Te lleva a través de los cinco grandes hoyos de List de una vez, ¡realmente hay hoyos en todas partes!  | Original
De la figura anterior, encontramos que los métodos como add / remove son en realidad de AbstractList, y java.util.Arrays $ ArrayList no anula el método de la clase padre. Y el método de la clase principal arrojará UnsupportedOperationException exactamente.
Te lleva a través de los cinco grandes hoyos de List de una vez, ¡realmente hay hoyos en todas partes!  | Original
Esta es la razón real por la que ArrayList no admite adiciones y eliminaciones.

¿Por qué usan su nueva Lista pero aún se afectan entre sí?

Además del pozo de que Li ghost ArrayList no admite operaciones de adición y eliminación, hay otro gran pozo: el cambio de los elementos internos afectará simultáneamente a la matriz original.
Te lleva a través de los cinco grandes hoyos de List de una vez, ¡realmente hay hoyos en todas partes!  | Original
Resultado de salida:


arrays:[modify_1, modify_2, 3]
list:[modify_1, modify_2, 3]

Como puede ver en la salida del registro, ya sea que modifiquemos la matriz original o la nueva colección List, las dos se afectarán entre sí.
Al observar la implementación de java.util.Arrays $ ArrayList, podemos encontrar que la capa inferior realmente usa la matriz original.

Te lleva a través de los cinco grandes hoyos de List de una vez, ¡realmente hay hoyos en todas partes!  | Original
Sabiendo la razón real, el método de reparación también es muy simple, ¡la muñeca tiene una capa de ArrayList!


List<String> list = new ArrayList<>(Arrays.asList(arrays));

Sin embargo, se siente muy engorroso escribir de esta manera, se recomienda utilizar el método proporcionado por Guava Lists.


List<String> list = Lists.newArrayList(arrays);

A través de los dos métodos anteriores, desacoplamos la nueva colección List de la matriz original y ya no nos afectamos entre sí. Al mismo tiempo, dado que todavía es una ArrayList real en este momento, no hay necesidad de preocuparse por agregar / eliminar errores.
Además de la interacción entre la nueva colección generada por Arrays # asList y la matriz original, otro método JDK List # subList genera la nueva colección e interactúa con la List original.
Veamos un ejemplo:
Te lleva a través de los cinco grandes hoyos de List de una vez, ¡realmente hay hoyos en todas partes!  | Original
registro de resultados de salida:


integerList:[10, 20, 3]
subList:[10, 20]

Al observar la implementación de List # subList, puede encontrar que hay un campo principal dentro de esta SubList para guardar la Lista original.
Te lleva a través de los cinco grandes hoyos de List de una vez, ¡realmente hay hoyos en todas partes!  | Original
Todas las acciones externas de lectura y escritura parecen estar operando la SubLista, pero las acciones subyacentes realmente ocurren en la Lista original, como el método de adición:
Te lleva a través de los cinco grandes hoyos de List de una vez, ¡realmente hay hoyos en todas partes!  | Original
Además, dado que la SubLista en realidad todavía hace referencia a la Lista original, durante el desarrollo comercial, si no presta atención, es probable que suceda Problema de OOM.

El siguiente ejemplo proviene de Geek Time: 100 errores comunes en el desarrollo empresarial de Java


private static List<List<Integer>> data = new ArrayList<>();

private static void oom() {
    for (int i = 0; i < 1000; i++) {
        List<Integer> rawList = IntStream.rangeClosed(1, 100000).boxed().collect(Collectors.toList());
        data.add(rawList.subList(0, 1));
    }
}

Parece que los datos finalmente guardan solo 1000 Listas con 1 elemento, lo que no ocupa mucho espacio. Pero el programa pronto será OOM.
La razón de OOM es precisamente porque cada SubList hace una fuerte referencia a una Lista original de 100.000 elementos, lo que hace imposible que el GC la recopile.
El método de reparación aquí también es muy simple. Al igual que el anterior, también tengo una muñeca y agrego una capa de ArrayList.
Te lleva a través de los cinco grandes hoyos de List de una vez, ¡realmente hay hoyos en todas partes!  | Original

Colección inmutable, si dices que no ha cambiado, ¿por qué cambiaste?

Para evitar que la colección List se maneje mal, podemos usar Colecciones # unmodifiableList para generar una colección inmutable para programación defensiva.
Esta colección inmutable solo se puede leer y no se puede modificar, incluida la adición, eliminación y modificación, para proteger la seguridad de la colección inmutable.
Te lleva a través de los cinco grandes hoyos de List de una vez, ¡realmente hay hoyos en todas partes!  | Original
Las últimas tres líneas de operaciones de escritura anteriores arrojarán todas las excepciones UnsupportedOperationException,
pero ¿cree que esto es seguro?
Te lleva a través de los cinco grandes hoyos de List de una vez, ¡realmente hay hoyos en todas partes!  | Original
Si alguien cambia accidentalmente la Lista original, encontrará que esta colección inmutable ha cambiado. . .
Te lleva a través de los cinco grandes hoyos de List de una vez, ¡realmente hay hoyos en todas partes!  | Original
Todos los resultados de las pruebas unitarias anteriores pasarán, lo que significa que las colecciones # unmodifiableList producirán colecciones inmutables que se verán afectadas por la lista original.
Verifique el método de implementación subyacente de Collections # unmodifiableList:
Te lleva a través de los cinco grandes hoyos de List de una vez, ¡realmente hay hoyos en todas partes!  | Original
puede ver que este es en realidad el mismo problema que la SubList anterior, y la Lista original se usa en la parte inferior de la nueva colección.
Dado que todas las operaciones de modificación de la colección inmutable reportarán errores, la colección inmutable no producirá ningún cambio, por lo que la colección original no se verá afectada. Pero no sirve para prevenirlo, la Lista original puede ser cambiada en cualquier momento, afectando así la colección inmutable.
Puede utilizar las dos formas siguientes para evitar la situación de lucirse.

Utilice el método JDK9 List # of.


List<String> list = new ArrayList<>(Arrays.asList("one", "two", "three"));
List<String> unmodifiableList = List.of(list.toArray(new String[]{}));

Usar lista inmutable de guayaba


List<String> list = new ArrayList<>(Arrays.asList("one", "two", "three"));
List<String> unmodifiableList = ImmutableList.copyOf(list);

En comparación, el método de la guayaba es relativamente refrescante y fácil de usar, se recomienda la guayaba para generar colecciones inmutables.

foreach añadir / eliminar elemento pit

Primero mira un fragmento de código:


String[] arrays = {"1", "2", "3"};
List<String> list = new ArrayList<>(Arrays.asList(arrays));
for (String str : list) {
    if (str.equals("1")) {
        list.remove(str);
    }
}

En el código anterior, utilizamos el método foreach para recorrer la colección List. Si se cumplen las condiciones, los elementos se eliminarán de la colección.
Este programa se compila normalmente, pero cuando se ejecuta, el programa será anormal. El registro es el siguiente:


java.util.ConcurrentModificationException
    at java.base/java.util.ArrayList$Itr.checkForComodification(ArrayList.java:939)
    at java.base/java.util.ArrayList$Itr.next(ArrayList.java:893)

Puede ver que el código en ArrayList $ Itr.next arroja el error final del programa, pero no llamamos a este método en el código. ¿Por qué?
En realidad, porque foreach es un tipo de sintaxis que Java nos proporciona. Azúcar, después de la compilación, se convertirá en otra forma.
Hagamos una compilación inversa del archivo de clase generado por el código anterior para ver cómo se ve el código final.
Te lleva a través de los cinco grandes hoyos de List de una vez, ¡realmente hay hoyos en todas partes!  | Original
Puede ver que foreach es en realidad el método de implementación de Iterator, razón por la cual la clase que atraviesa foreach necesita implementar la interfaz Iterator.
Luego, veamos el método para lanzar una excepción:
Te lleva a través de los cinco grandes hoyos de List de una vez, ¡realmente hay hoyos en todas partes!  | Original
esperabaModCount proviene del método list # iterator:
Te lleva a través de los cinco grandes hoyos de List de una vez, ¡realmente hay hoyos en todas partes!  | Original
es decir, esperabaModCount == modCount cuando atravesamos el bucle por primera vez, veamos modCount a continuación.
modCount se deriva de AbstractList, la clase principal de ArrayList, y se puede utilizar para registrar el número de veces que se ha modificado la colección List.
Te lleva a través de los cinco grandes hoyos de List de una vez, ¡realmente hay hoyos en todas partes!  | Original
Después de ArrayList # remove, modCount aumentará en uno, y esperabaModCount y modCount no serán iguales, lo que provocará un error cuando se atraviese el iterador.
Te lleva a través de los cinco grandes hoyos de List de una vez, ¡realmente hay hoyos en todas partes!  | Original

La operación de recuento de modCount la realizará la propia subclase, y cada operación de modificación (adición o eliminación) de ArrayList aumentará modCount en 1. Pero como CopyOnWriteArrayList no usa modCount para contar.
Entonces, CopyOnWriteArrayList usa foreach para eliminarlo es seguro, pero se recomienda usar los siguientes dos elementos de eliminación, operación unificada.

Hay dos formas de reparar:

Use Iterator # remove para eliminar elementos

Te lleva a través de los cinco grandes hoyos de List de una vez, ¡realmente hay hoyos en todas partes!  | Original
iterador

JDK1.8 Lista # removeIf

Te lleva a través de los cinco grandes hoyos de List de una vez, ¡realmente hay hoyos en todas partes!  | Original
Se recomienda utilizar JDK1.8, que es conciso y claro.

Pensando

Si simplemente modifico la condición de juicio del código foreach anterior:
Te lleva a través de los cinco grandes hoyos de List de una vez, ¡realmente hay hoyos en todas partes!  | Original
Ejecute este código y verá que este código no informará un error. ¿Es sorprendente?
Los estudiantes interesados ​​pueden estudiar el código fuente por sí mismos, o ver directamente el artículo de @why 技术: ¡
Esta pregunta básica de Java es realmente un pozo! Te lo ruego, piensa bien antes de contestar.
¡Esta pregunta básica de Java es realmente complicada! No esperaba una secuela.

para resumir

Primero, no debemos estar preconcebidos y asumir que Arrays.asList y List.subList son simplemente ArrayLists ordinarios e independientes.
Si no hay alternativa, use Arrays.asList y List.subList. Cuando regrese a otros métodos, debe recordar anidar una capa de java.util.ArrayList real.
Las colecciones inmutables proporcionadas por el segundo JDK son en realidad muy engorrosas, ineficientes e inseguras, por lo que se recomienda utilizar las colecciones inmutables de Guava en su lugar.
Finalmente, recuerde no agregar / eliminar elementos en foreach.

Finalmente (buscando elogios, buscando atención)

En qué pozos ha pisado durante el uso de la colección List, deje un mensaje para discutir.
Soy el hermanito negro de abajo, nos vemos en el próximo artículo ~

Supongo que te gusta

Origin blog.51cto.com/15077551/2595174
Recomendado
Clasificación