Guía para recopiladores de Java 8: toSet()

En esta guía, aprenda a recopilar y convertir un flujo en un Conjunto en Java 8, 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 elementos Stream en un mapa en Java 8.

Coleccionistas y Stream.collect()

Los recopiladores representan implementaciones de la interfaz Collector, que implementa varias operaciones de reducción útiles, como acumular elementos en colecciones, resumir elementos en función de un parámetro específico, etc.

Todas las implementaciones predefinidas se pueden encontrar dentro de la clase Collectors.

Sin embargo, también puede implementar muy fácilmente su propio recopilador y usarlo en lugar de los predefinidos; puede llegar bastante lejos con los recopiladores integrados, ya que cubren la gran mayoría de los casos en los que es posible que desee usarlos.

Para poder usar la clase en nuestro código necesitamos importarla:

1
import static java.util.stream.Collectors.*;

Stream.collect() realiza una operación de reducción mutable en los elementos de la secuencia.

{.icon aria-hidden=“true”}

Una operación de reducción mutable recopila elementos de entrada en un contenedor mutable, como una Colección, mientras procesa los elementos de la secuencia.

Guía de Collectors.toSet()

El método toSet() se utiliza para recopilar un flujo en un conjunto. Funciona de manera similar al método toList(), pero finalmente recopila en una estructura de datos subyacente diferente, devolviendo un Collector que acumula los elementos de entrada en un nuevo Set.

Vale la pena señalar que no hay garantías sobre el tipo, la mutabilidad, la serialización o la seguridad de subprocesos del ‘Conjunto’ devuelto:

1
public static <T> Collector<T,?,Set<T>> toSet()

Un ‘Conjunto’ no permite elementos duplicados o, en términos más formales, los conjuntos no contienen un par de elementos ‘a’ y ‘b’ tales que ‘a.es igual a (b)’, y puede contener ** como máximo ** un elemento nulo.

Si recopila una secuencia con elementos duplicados en un ‘Conjunto’, es una forma rápida de eliminar los duplicados:

1
2
3
Stream<String> stream = 
    Stream.of("This", "forms", "forms", "a", "short", "a", "sentence", "sentence");
Set<String> sentenceSet = stream.collect(Collectors.toSet());

Sin embargo, este ejemplo destaca una característica importante de cómo se completan los conjuntos: los elementos no conservan su orden relativo cuando se recopilan, como ocurre, por ejemplo, en el recopilador toList(). Esto se debe a que la implementación predeterminada de un Set es un HashSet, que ordena los elementos en función de sus valores hash y ni siquiera garantiza la coherencia de este orden a lo largo del tiempo.

Echaremos un vistazo a cómo podemos proporcionar una implementación personalizada de un Conjunto en una sección posterior.

Ejecutar este fragmento de código da como resultado:

1
[sentence, a, This, short, forms]

Dado que rara vez trabajamos solo con cadenas u objetos primitivos, definamos una clase simple para representar un Libro:

1
2
3
4
5
6
7
8
public class Book {
    private String title;
    private String author;
    private int releaseYear;
    private int soldCopies;

    // Constructor, getters and setters
}

Y con eso, vamos a crear una lista simple de algunos libros (con una entrada duplicada):

1
2
3
4
5
6
7
List<Book> books = Arrays.asList(
    new Book("The Fellowship of the Ring", "J.R.R. Tolkien", 1954, 30),
    new Book("The Hobbit", "J.R.R. Tolkien", 1937, 40),
    new Book("Animal Farm", "George Orwell", 1945, 32),
    new Book("Nineteen Eighty-Four", "George Orwell", 1949, 50),
    new Book("Nineteen Eighty-Four", "George Orwell", 1949, 38)
);

Una canalización bastante típica consiste en un filtro() basado en algún Predicado antes de volver a recopilar en una colección:

1
2
3
4
5
6
Set<String> booksGeorgeOrwell = books.stream()
                .filter(book->book.getAuthor()
                .equals("George Orwell") && book.getCopiesSold() >= 30)
                .map(Book::getTitle).collect(Collectors.toSet());

System.out.println(booksGeorgeOrwell);

If you'd like to read more about filtering and predicates - read our Java 8 Streams: Guía definitiva del método filter() and Programación Funcional en Java 8: Guía Definitiva de Predicados!

Como discutimos anteriormente, Sets no permite duplicados. Después de que hayamos inspeccionado la cláusula que hemos dado y cuál de los libros en nuestra Lista cumple con los criterios, deberíamos tener el siguiente resultado:

1
[Animal Farm, Nineteen Eighty-Four]

¡Funciona genial! La entrada duplicada se eliminó, sin una lógica explícita para eliminar los duplicados.

Conclusión

En esta guía, hemos echado un vistazo a cómo recopilar y convertir un flujo en un conjunto, utilizando la API de flujos de Java 8, con la clase Collectors.

Licensed under CC BY-NC-SA 4.0