Java 8: recopile la transmisión en una lista, un conjunto o un mapa no modificables

En esta guía, aprenda cómo convertir/recopilar un flujo de Java 8 en una colección, como una lista, un mapa o un conjunto, ¡con ejemplos prácticos de código!

Introducción

Una secuencia representa una secuencia de elementos y admite diferentes tipos de operaciones que conducen al resultado deseado. La fuente de un flujo suele ser una Colección o un Array, desde el cual se transmiten los datos.

Los flujos se diferencian de las colecciones en varios aspectos; sobre todo porque los flujos no son una estructura de datos que almacena elementos. Son de naturaleza funcional, y vale la pena señalar que las operaciones en un flujo producen un resultado y, por lo general, devuelven otro flujo, pero no modifican su origen.

Para "solidificar" los cambios, reúne los elementos de un flujo en una Colección.

En esta guía, veremos cómo recopilar una secuencia en colecciones no modificables.

Recopilar flujo en colecciones no modificables

Vale la pena señalar que hay una diferencia entre una colección inmutable y no modificable.

No puedes cambiar el contenido de una colección no modificable. Pero, si la colección de origen cambia, la colección no modificable también cambia. Una colección inmutable es aquella que resulta de copiar una colección fuente para crear una nueva. Este nuevo también debería ser inmodificable.

En las secciones siguientes, veremos cómo puede recopilar una secuencia en una lista, un conjunto o un mapa no modificables. Para este propósito, los métodos regulares collect() y collectingAndThen() hacen el truco. El primero le permite convertir directamente un flujo en una colección, mientras que el segundo nos permite recopilar un flujo en una colección regular y luego convertirlo en su contraparte no modificable a través de una función separada.

En su lugar, podría introducir otras funciones o encadenar el método collectingAndThen() para introducir nuevos cambios en la canalización antes de recopilar en una colección no modificable.

If you'd like to read more about the regular collect() and advanced collectingAndThen(), read our Secuencias de Java 8: convertir una secuencia en lista and Guía para recopiladores de Java 8: coleccionar y luego ()!

Recopilar flujo en lista no modificable

Comencemos con una lista. Usaremos el recopilador estándar Collectors.toList(), seguido de una llamada a unmodifiableList() de la clase Collections. Alternativamente, puede proporcionar un recopilador toUnmodifiableList() al método collect():

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
Stream<Integer> intStream = Stream.of(1, 2, 3);

List<Integer> unmodifiableIntegerList1 = intStream.collect(Collectors.toUnmodifiableList());

List<Integer> unmodifiableIntegerList2 = intStream.collect(
        Collectors.collectingAndThen(
                Collectors.toList(),
                Collections::unmodifiableList
        )
);

Si intentamos modificar estas listas, debería lanzarse una UnsupportedOperationException. Su simpleName debería ser UnmodifiableRandomAccessList y deberían contener exactamente los mismos elementos que se ven en la transmisión:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
@Test
public void listShouldBeImmutable() {
    // Should contain elements 1, 2, and 3
    assertEquals(
        "[1, 2, 3]",
        unmodifiableIntegerList1 .toString()
    );
    // Should be of type UnmodifiableList
    assertEquals(
        "UnmodifiableRandomAccessList",
        unmodifiableIntegerList1 .getClass().getSimpleName()
    );
    // Should throw an exception when you attempt to modify it
    assertThrows(
        UnsupportedOperationException.class,
        () -> unmodifiableIntegerList1 .add(4)
    );
}

Recopilar transmisión en conjunto no modificable

Si una transmisión con la que está tratando tiene duplicados y desea deshacerse de ellos, la forma más fácil no es filtrar la lista o realizar un seguimiento de los elementos encontrados en otra lista. La solución más fácil para eliminar duplicados de una lista es encuadrar la lista en un conjunto, ¡lo que no permite duplicados!

Una vez más, el recopilador collectingAndThen() hace maravillas aquí, ya que puede recopilar la transmisión en un Conjunto y convertirlo en un conjunto no modificable en la función descendente:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
Stream<Integer> intStream = Stream.of(1, 1, 3, 2, 3);

Set<Integer> integerSet1 = intStream.collect(Collectors.toUnmodifiableSet());

Set<Integer> integerSet2 = intStream.collect(
        Collectors.collectingAndThen(
                Collectors.toSet(),
                Collections::unmodifiableSet
        )
);

Entonces, el ‘Conjunto’ debería ser inmodificable. Cualquier intento de cambio debería lanzar una UnsupportedOperationException:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
@Test
public void setShouldBeImmutable() {
    // Set shouldn't contain duplicates
    assertEquals(
        "[1, 2, 3]",
        integerSet1.toString()
    );
    // Set should be of type UnmodifiableSet
    assertEquals(
        "UnmodifiableSet",
        integerSet1.getClass().getSimpleName()
    );
    // Set should not be modifiable
    assertThrows(
        UnsupportedOperationException.class,
        () -> integerSet1.add(3)
    );
}

Recopilar transmisión en mapa no modificable

Recolectar en un mapa no modificable funciona de la misma manera que los dos anteriores, así que intentemos darle un poco de sabor. Digamos que tiene un caso en el que desea almacenar números y sus equivalentes de valor cuadrado:


Valor clave 2 4 3 9 4 16


Pero, cuando recibe claves duplicadas, no desea repetir las entradas:


¿Pases de valor clave? 2 4 SI 3 9 SI 4 16 SI 4 16 NO


Sin embargo, el método, cuando se convierte en un mapa usando el mismo enfoque que usamos antes, no hay lugar para verificar si hay entradas duplicadas:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
Stream<Integer> stream = Stream.of(1, 2, 3, 4, 4);

Map<Integer, Integer> map1 = stream.collect(
        Collectors.toUnmodifiableMap(
                Function.identity(), 
                i -> (int)Math.pow(i, 2)
        )
);

Map<Integer, Integer> map2 = stream.collect(
        Collectors.collectingAndThen(
                Collectors.toMap(
                        // Key
                        Function.identity(),
                        // Value
                        i -> (int) Math.pow(i, 2)
                ),
                Collections::unmodifiableMap
        )
);

Tenga en cuenta el uso de Function.identity() en el mapeador de claves del método Collectors.toMap(). El método identity() hace que el mapeador use el propio elemento Integer como la clave de la entrada del mapa.

Por lo tanto, cuando lo llama con entradas duplicadas, siempre lanza una IllegalStateException:

1
2
Exception in thread "main" java.lang.IllegalStateException: 
Duplicate key 4 (attempted merging values 16 and 16)

Es fácil remediar este problema con las propias operaciones de transmisión, por lo que el cliente no tiene que preocuparse por proporcionar una lista limpia. Simplemente agregando una operación distinct() intermedia a la transmisión, podemos filtrar los valores duplicados antes de recopilarlos:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
Map<Integer, Integer> map1 = stream.distinct().collect(
        Collectors.toUnmodifiableMap(
                Function.identity(), 
                i -> (int)Math.pow(i, 2)
        )
);

Map<Integer, Integer> map2 = stream.distinct().collect(
        Collectors.collectingAndThen(
                Collectors.toMap(
                        // Key
                        Function.identity(),
                        // Value
                        i -> (int) Math.pow(i, 2)
                ),
                Collections::unmodifiableMap
        )
);

Probemos el resultado:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
@Test
public void mapShouldBeImmutable() {    
    assertEquals(
        "{1=1, 2=4, 3=9, 4=16}",
        map1.toString()
    );
    assertEquals(
        "UnmodifiableMap",
        map1.getClass().getSimpleName()
    );
    assertThrows(
        UnsupportedOperationException.class,
        () -> map1.put(5, 25)
    );
}

Conclusión

En esta breve guía, hemos echado un vistazo a cómo recopilar secuencias en colecciones no modificables: ¡una lista, un conjunto y un mapa!

También echamos un vistazo rápido a cómo manejar los valores duplicados, que pueden generar excepciones en algunas estructuras de datos y, en otras, provocar fallas silenciosas.

Licensed under CC BY-NC-SA 4.0