¿Cuál es la diferencia entre List.of y Arrays.asList?

117

Java 9 introdujo nuevos métodos de fábrica para listas List.of:

List<String> strings = List.of("first", "second");

¿Cuál es la diferencia entre la opción anterior y la nueva? Es decir, cuál es la diferencia entre esto:

Arrays.asList(1, 2, 3);

y esto:

List.of(1, 2, 3);
ZhekaKozlov
fuente
1
Vea también esta charla de Stuart "Beaker" Marks.
user1803551
20
@ user1803551 Aunque entiendo su frustración, este razonamiento podría sentar un precedente realmente no deseado. Muchas preguntas aquí tienen una respuesta que está 'claramente establecida' (dependiendo de cómo se defina esto). Le insto a que lleve esta discusión a meta, pero estoy bastante seguro de que tal discusión ya debería existir (y espero que alguien pueda encontrarla y vincularla :-)
Dimitris Fasarakis Hilliard
4
@ user1803551 Javadocs no menciona la diferencia entre los detalles de implementación de estos dos métodos (como el consumo de espacio o el rendimiento). Creo que a la gente también le gustaría conocer estos detalles.
ZhekaKozlov
5
@ZhekaKozlov La respuesta aceptada y súper votada tampoco. ¿Qué le dice eso sobre los estándares aceptados? Incluso tiene menos información que en los documentos (serialización, identidad, pedidos). En todo caso, presente una solicitud a OpenJDK para agregar esa información.
user1803551
3
Esta pregunta se está discutiendo en meta .
Dimitris Fasarakis Hilliard

Respuestas:

172

Arrays.asListdevuelve una lista mutable mientras que la lista devuelta por List.ofes inmutable :

List<Integer> list = Arrays.asList(1, 2, null);
list.set(1, 10); // OK

List<Integer> list = List.of(1, 2, 3);
list.set(1, 10); // Fails with UnsupportedOperationException

Arrays.asListpermite elementos nulos mientras List.ofque no:

List<Integer> list = Arrays.asList(1, 2, null); // OK
List<Integer> list = List.of(1, 2, null); // Fails with NullPointerException

contains se comporta de manera diferente con nulos:

List<Integer> list = Arrays.asList(1, 2, 3);
list.contains(null); // Returns false

List<Integer> list = List.of(1, 2, 3);
list.contains(null); // Fails with NullPointerException

Arrays.asListdevuelve una vista de la matriz pasada, por lo que los cambios a la matriz también se reflejarán en la lista. Porque List.ofesto no es cierto:

Integer[] array = {1,2,3};
List<Integer> list = Arrays.asList(array);
array[1] = 10;
System.out.println(list); // Prints [1, 10, 3]

Integer[] array = {1,2,3};
List<Integer> list = List.of(array);
array[1] = 10;
System.out.println(list); // Prints [1, 2, 3]
ZhekaKozlov
fuente
22
Para que una lista se comporte de manera diferente en función de cómo se construye, no me parece muy orientada a objetos. Quizás si List.of devolviera un tipo ImmutableList, esto tendría sentido. Esta es una abstracción muy escasa aquí.
Sandy Chapman
5
No soy un desarrollador de Java, así que tómalo como una observación casual. Posiblemente haya una buena razón para que el comportamiento sea diferente, pero si tuviera un método que devolviera un List <Integer> como el ejemplo, la interfaz no sería suficiente para saber si obtendré una excepción de tiempo de ejecución si la verifico. para nulos. Del mismo modo, un cambio en la implementación de esos métodos podría afectar el código distante del sitio de llamada de mi método si esa verificación ocurre en otro lugar. @Nicolai
Sandy Chapman
8
@SandyChapman, esto podría ser un comportamiento inesperado para algunos (¿o la mayoría?), Pero es un comportamiento documentado. Desde el List.contains(Object o)javadoc del ' : "Lanza [...] NullPointerException - si el elemento especificado es nulo y esta lista no permite elementos nulos (opcional)". O de la extensa introducción de la interfaz que pocos leen: "Algunas implementaciones de colección tienen restricciones sobre los elementos que pueden contener"
Aaron
11
@Aaron bueno, al menos es una abstracción con fugas bien documentada :)
Sandy Chapman
6
Chapman @Sandy: List.of no volver algún ImmutableListtipo, su nombre real es sólo un detalle de implementación no pública. Si era público y alguien lo lanzaba de Listnuevo, ¿dónde estaba la diferencia? ¿Dónde está la diferencia con Arrays.asList, que devuelve una Listimplementación no pública , que arroja una excepción al intentar addo remove, o la lista devuelta por la Collections.unmodifiableListque no permite ninguna modificación? Se trata de contratos especificados en la Listinterfaz. Las interfaces de Colecciones con métodos opcionales siempre fueron impuras OOP desde Java 1.2…
Holger
31

Las diferencias entre Arrays.asListyList.of

Vea JavaDocs y esta charla de Stuart Marks (o versiones anteriores).

Usaré lo siguiente para los ejemplos de código:

List<Integer> listOf = List.of(...);
List<Integer> asList = Arrays.asList(...);
List<Integer> unmodif = Collections.unmodifiableList(asList);

Inmutabilidad estructural (O: inmodificabilidad)

Cualquier intento de cambiar estructuralmenteList.of resultará en un UnsupportedOperationException. Eso incluye operaciones como agregar , configurar y eliminar . Sin embargo, puede cambiar el contenido de los objetos en la lista (si los objetos no son inmutables), por lo que la lista no es "completamente inmutable".

Este es el mismo destino para las listas no modificables creadas con Collections.unmodifiableList. Solo esta lista es una vista de la lista original, por lo que puede cambiar si cambia la lista original.

Arrays.asListno es completamente inmutable, no tiene restricciones set.

listOf.set(1, "a");  // UnsupportedOperationException
unmodif.set(1, "a"); // UnsupportedOperationException
asList.set(1, "a");  // modified unmodif! unmodif is not truly unmodifiable

De manera similar, cambiar la matriz de respaldo (si la mantiene presionada) cambiará la lista.

La inmutabilidad estructural viene con muchos efectos secundarios relacionados con la codificación defensiva, la concurrencia y la seguridad que están más allá del alcance de esta respuesta.

Hostilidad nula

List.ofy cualquier colección desde Java 1.5 no se permite nullcomo elemento. Intentar pasar nullcomo un elemento o incluso una búsqueda resultará en un NullPointerException.

Dado que Arrays.asListes una colección de 1.2 (el marco de colecciones), permite nulls.

listOf.contains(null);  // NullPointerException
unmodif.contains(null); // allowed
asList.contains(null);  // allowed

Forma serializada

Dado que List.ofse introdujo en Java 9 y las listas creadas por este método tienen su propia forma serializada (binaria), no se pueden deserializar en versiones anteriores de JDK (sin compatibilidad binaria ). Sin embargo, puede eliminar / serializar con JSON, por ejemplo.

Identidad

Arrays.asListllamadas internas new ArrayList, lo que garantiza la desigualdad de referencia.

List.ofdepende de la implementación interna. Las instancias devueltas pueden tener igualdad de referencia, pero como esto no está garantizado, no puede confiar en ellas.

asList1 == asList2; // false
listOf1 == listOf2; // true or false

Vale la pena mencionar que las listas son iguales (vía List.equals) si contienen los mismos elementos en el mismo orden, independientemente de cómo fueron creadas o qué operaciones soportan.

asList.equals(listOf); // true i.f.f. same elements in same order

Implementación (advertencia: los detalles pueden cambiar según las versiones)

Si el número de elementos en la lista de List.ofes 2 o menos, los elementos se almacenan en campos de una clase especializada (interna). Un ejemplo es la lista que almacena 2 elementos (fuente parcial):

static final class List2<E> extends AbstractImmutableList<E> {
    private final E e0;
    private final E e1;

    List2(E e0, E e1) {
        this.e0 = Objects.requireNonNull(e0);
        this.e1 = Objects.requireNonNull(e1);
    }
}

De lo contrario, se almacenan en una matriz de forma similar a Arrays.asList.

Eficiencia de tiempo y espacio

Las List.ofimplementaciones que se basan en el campo (tamaño <2) funcionan un poco más rápido en algunas operaciones. Como ejemplos, size()puede devolver una constante sin obtener la longitud de la matriz y contains(E e)no requiere una sobrecarga de iteración.

La construcción de una lista no modificable vía List.oftambién es más rápida. Compare el constructor anterior con 2 asignaciones de referencia (e incluso la de una cantidad arbitraria de elementos) para

Collections.unmodifiableList(Arrays.asList(...));

que crea 2 listas más otros gastos generales. En términos de espacio, ahorra el UnmodifiableListenvoltorio más algunos centavos. En última instancia, los ahorros en el HashSetequivalente son más convincentes.


Tiempo de conclusión: utilícelo List.ofcuando desee una lista que no cambie y Arrays.asListcuando desee una lista que pueda cambiar (como se muestra arriba).

usuario1803551
fuente
1
Para las personas que se preguntan por qué existe esta respuesta, consulte esto .
user1803551
3
Arrays.asListno es completamente mutable. asList.add(1);lanza un UnsupportedOperationException.
mapeters
"Null hostile" es una excelente manera de decirlo. Prácticamente no puedo usar el List.oftiempo que la gente quiera llamar containsy no ser sorprendido por una NullPointerException.
Noumenon
14

Resumamos las diferencias entre List.of y Arrays.asList

  1. List.ofse puede utilizar mejor cuando el conjunto de datos es menor y sin cambios, mientras que Arrays.asListse puede utilizar mejor en el caso de un conjunto de datos grande y dinámico.

  2. List.ofocupa mucho menos espacio de sobrecarga porque tiene una implementación basada en el campo y consume menos espacio de pila, tanto en términos de sobrecarga fija como por elemento. mientras que Arrays.asListocupa más espacio de sobrecarga porque durante la inicialización crea más objetos en el montón.

  3. La colección devuelta por List.ofes inmutable y, por lo tanto, segura para subprocesos, mientras que la colección devuelta por Arrays.asListes mutable y no segura para subprocesos. (Las instancias de colección inmutables generalmente consumen mucha menos memoria que sus contrapartes mutables).

  4. List.ofno permite elementos nulos mientras que Arrays.asListpermite elementos nulos .

Mohit Tyagi
fuente
2
"Las instancias de colección inmutables generalmente consumen mucha menos memoria que sus contrapartes mutables". - ¿De Verdad? ¿Le importaría desarrollar un poco sobre eso? ¿Quiere decir que se pueden compartir de forma segura, o quiere decir que las instancias en sí mismas se pueden implementar de alguna manera de manera más eficiente?
Hulk
1
@Hulk El que responde tiene razón sobre la eficiencia del espacio. Vea la charla de Stuart Marks: youtu.be/q6zF3vf114M?t=49m48s
ZhekaKozlov
2
@ZhekaKozlov Eso parece ser cierto en general, pero soy muy escéptico de que sea cierto cuando se habla de Arrays.asListversus List.of, dado que el primero es literalmente una envoltura alrededor de una matriz. Al menos, la implementación de OpenJDK parece tener una sobrecarga extremadamente pequeña. De hecho, List.ofnecesitaría hacer copias de cualquier matriz pasada, por lo que, a menos que la matriz en sí vaya a ser GC pronto, parecería que List.oftiene una huella de memoria significativamente mayor.
Chris Hayes
4
@ChrisHayes Al menos List.of(x)y List.of(x, y)son más eficientes porque no asignan matrices en absoluto
ZhekaKozlov
2
@Hulk: no olvides que los List.ofmétodos no son necesarios para devolver listas nuevas cada vez. Estas listas tienen una identidad no especificada, por lo que podría haber almacenamiento en caché o deduplicación o escalarización manejada en el nivel de JVM. Si no está en esta versión, quizás en la siguiente. Está permitido por el contrato. Por el contrario, Array.asListdepende de la identidad de la matriz que está pasando, ya que la lista resultante es una vista mutable en la matriz, que refleja todos los cambios bidireccionales.
Holger
3

Aparte de las respuestas anteriores, hay ciertas operaciones en las que ambos List::ofy Arrays::asListdifieren:

+----------------------+---------------+----------+----------------+---------------------+
|      Operations      | SINGLETONLIST | LIST::OF | ARRAYS::ASLIST | JAVA.UTIL.ARRAYLIST |
+----------------------+---------------+----------+----------------+---------------------+
|          add         |             |       |              |          ✔️          |
+----------------------+---------------+----------+----------------+---------------------+
|        addAll        |             |       |              |          ✔️          |
+----------------------+---------------+----------+----------------+---------------------+
|         clear        |             |       |              |          ✔️          |
+----------------------+---------------+----------+----------------+---------------------+
|        remove        |             |       |              |          ✔️          |
+----------------------+---------------+----------+----------------+---------------------+
|       removeAll      |       ❗️       |        |        ❗️       |          ✔️          |
+----------------------+---------------+----------+----------------+---------------------+
|       retainAll      |       ❗️       |       |        ❗️        |          ✔️          |
+----------------------+---------------+----------+----------------+---------------------+
|      replaceAll      |             |       |        ✔️       |          ✔️          |
+----------------------+---------------+----------+----------------+---------------------+
|          set         |             |       |        ✔️       |          ✔️          |
+----------------------+---------------+----------+----------------+---------------------+
|         sort         |       ✔️       |        |        ✔️      |          ✔️          |
+----------------------+---------------+----------+----------------+---------------------+
|  remove on iterator  |             |       |              |          ✔️          |
+----------------------+---------------+----------+----------------+---------------------+
| set on list-iterator |             |       |        ✔️       |          ✔️          |
+----------------------+---------------+----------+----------------+---------------------+
  1. ✔️ significa que el método es compatible
  2. ❌ significa que llamar a este método arrojará una UnsupportedOperationException
  3. ❗️ significa que el método es compatible solo si los argumentos del método no causan una mutación, por ejemplo, Collections.singletonList ("foo"). RetenerAll ("foo") está bien, pero Collections.singletonList ("foo"). RetenerAll ("bar") ) arroja una UnsupportedOperationException

Más sobre colecciones :: singletonList Vs. Lista de

Vishwa Ratna
fuente
1
respuesta para el examen de Java
povis