¿Diferencias de Stream.toList() y Stream.collect(Collectors.toList()) de Java 16?

5 minutos de lectura

avatar de usuario de knittl
tejer

JDK 16 ahora incluye un toList() método directamente en Stream instancias. En versiones anteriores de Java, siempre tenía que usar el collect método y proporcionar un Collector instancia.

El nuevo método obviamente tiene menos caracteres para escribir. ¿Ambos métodos son intercambiables o hay diferencias sutiles que uno debe tener en cuenta?

var newList = someCollection.stream()
    .map(x -> mapX(x))
    .filter(x -> filterX(x))
    .toList();

// vs.

var oldList = someCollection.stream()
    .map(x -> mapX(x))
    .filter(x -> filterX(x))
    .collect(Collectors.toList());

(Esta pregunta es similar a ¿Funcionaría mejor Stream.toList() que Collectors.toList(), pero se centra en el comportamiento y no (solo) en el rendimiento).

Avatar de usuario de Arvind Kumar Avinash
Arvind Kumar Avinash

Una diferencia es que Stream.toList() provee un List implementación que es inmutable (tipo ImmutableCollections.ListN que no se pueden agregar o clasificar) similar a la proporcionada por List.of() y en contraste con el mutable (se puede cambiar y ordenar) ArrayList proporcionado por Stream.collect(Collectors.toList()).

Manifestación:

import java.util.stream.Stream;
import java.util.List;

public class Main {
    public static void main(String[] args) {
        List<String> list = Stream.of("Hello").toList();
        System.out.println(list);
        list.add("Hi");
    }
}

Producción:

[Hello]
Exception in thread "main" java.lang.UnsupportedOperationException
    at java.base/java.util.ImmutableCollections.uoe(ImmutableCollections.java:142)
    at java.base/java.util.ImmutableCollections$AbstractImmutableCollection.add(ImmutableCollections.java:147)
    at Main.main(Main.java:8)

por favor, compruebe Este artículo para más detalles.

Actualizar:

Curiosamente, Stream.toList() devuelve un nulllista que contiene s con éxito.

import java.util.stream.Stream;
import java.util.List;

public class Main {
    public static void main(String[] args) {
        List<Object> list = Stream.of(null, null).toList();
        System.out.println(list);
    }
}

Producción:

[null, null]

Por otro lado, List.of(null, null) lanza NullPointerException.

import java.util.List;

public class Main {
    public static void main(String[] args) {
        List<Object> list = List.of(null, null);
    }
}

Producción:

Exception in thread "main" java.lang.NullPointerException
    at java.base/java.util.Objects.requireNonNull(Objects.java:208)
    at java.base/java.util.ImmutableCollections$List12.<init>(ImmutableCollections.java:453)
    at java.base/java.util.List.of(List.java:827)
    at Main.main(Main.java:5)

Nota: he usado openjdk-16-ea+34_osx-x64 para compilar y ejecutar el código Java SE 16.

Recursos útiles:

  1. Error de JDK #JDK-8180352
  2. ¿Llamar al método varargs de Java con un solo argumento nulo?

  • IIRC, Collectors.toList() tampoco se garantiza que nos proporcione una lista mutable. Simplemente sucede que lo hace en las versiones de Java que hemos visto hasta ahora.

    – Olé VV

    30 de enero de 2021 a las 18:14

  • @OleV.V. – Correcto. El artículo vinculado en mi respuesta lo menciona como: Although there are no guarantees regarding the “type, mutability, serializability, or thread-safety” on the List provided by Collectors.toList(), it is expected that some may have realized it’s currently an ArrayList and have used it in ways that depend on the characteristics of an ArrayList.

    – Arvind Kumar Avinash

    30 de enero de 2021 a las 18:26

  • @Caramiriel La List La interfaz se diseñó a propósito con una serie de métodos opcionales, es decir, métodos que las clases de implementación no necesitan implementar y que muchas implementaciones no implementan. No eres el primero en preguntarse.

    – Olé VV

    4 de febrero de 2021 a las 20:44

  • No es realmente una afirmación correcta decir que collect(toList()) devuelve una lista mutable; la especificación es muy clara que hace sin garantías en cuanto a la mutabilidad de la lista devuelta. La implementación actual pasa a devolver un ArrayList ahora mismo, pero la especificación se escribió explícitamente para permitir que eso cambie. Si quieres una lista mutable, usa toCollection(ArrayList::new).

    – Brian Goetz

    19 de febrero de 2021 a las 17:07

  • Es desafortunado que List no se dividió en ReadableList con get() etc y WritableList con set(), add() etc., pero es demasiado tarde para arreglarlo ahora. (nota: originalmente vine aquí para sugerir toCollection(ArrayList::new), que es lo que siempre uso cuando necesito una lista mutable, pero el hombre mismo se me adelantó. 😂)

    – Clemente Cherlin

    22 de febrero de 2021 a las 14:05


Avatar de usuario de ZhekaKozlov
ZhekaKozlov

Aquí hay una pequeña tabla que resume las diferencias entre Stream.collect(Collectors.toList()), Stream.collect(Collectors.toUnmodifiableList()) y Stream.toList():

Método Garantiza la inmodificabilidad Permite nulos
collect(toList()) No
collect(toUnmodifiableList()) No
toList()

Otra pequeña diferencia:

// Compiles
List<CharSequence> list = Stream.of("hello", "world").collect(toList());

// Error
List<CharSequence> list = Stream.of("hello", "world").toList();

  • El nuevo método Stream.toList() no produce una lista no modificable ni un atajo para recopilar (toUnmodifiableList()), porque toUnmodifiableList() no acepta valores nulos. La implementación de Stream.toList() no está restringida por la interfaz Collector; por lo tanto, Stream.toList() asigna menos memoria. Eso lo hace óptimo para usar cuando el tamaño de la transmisión se conoce de antemano. blogs.oracle.com/javamagazine/hidden-gems-jdk16-jdk17-jep ¿Puedes validar las declaraciones anteriores?

    – deepakl.2000

    28 de julio de 2021 a las 18:19


  • Agregaría que el primero se introdujo en Java 8, el segundo en Java 10 y el tercero en Java 16.

    –Guillaume Husta

    5 de diciembre de 2022 a las 10:05

.collect(toList()) y toList() comportarse diferente con respecto a la compatibilidad de subtipo de los elementos en las listas creadas.

Echa un vistazo a las siguientes alternativas:

  • List<Number> old = Stream.of(0).collect(Collectors.toList()); funciona bien, aunque recopilamos un flujo de Entero en una lista de Número.
  • List<Number> new = Stream.of(0).toList(); es la versión equivalente de Java 16+, pero no compila (cannot convert from List<Integer> to List<Number>; al menos en ecj, el compilador Java de Eclipse).

Hay al menos 2 soluciones para corregir el error de compilación:

  • Emitir explícitamente al tipo buscado
    List<Number> fix1 = Stream.of(0).map(Number.class::cast).toList();
  • permitir subtipos en la colección de resultados:
    List<? extends Number> fix2 = Stream.of(0).toList();

Según tengo entendido, la causa raíz es la siguiente: el tipo genérico T de Java 16 toList() es el mismo que el tipo genérico T del propio Stream. Sin embargo, el tipo genérico T de Collectors.toList() se propaga desde el lado izquierdo de la asignación. Si esos 2 tipos son diferentes, es posible que vea errores al reemplazar todas las llamadas anteriores.

¿Ha sido útil esta solución?