Java Collect(): como utilizar operações de redução
Os Stream Collectors são uma poderosa funcionalidade da API Java 8 Stream que permite recolher e processar dados de forma eficiente. Explicamos a estrutura e as possíveis utilizações do método Java Collect.
Quais são os âmbitos de aplicação do Java Collect()?
Um Stream Collector pode ser utilizado para criar uma lista, um conjunto ou um mapa a partir de um fluxo (stream). Um fluxo é uma sequência de elementos que são processados um após o outro. A interface do Collector fornece uma série de operações de redução para os dados num fluxo de canalização. Estas são operações finais que recolhem e fundem os resultados das etapas intermédias.
Os coletores podem ser usados, por exemplo, para filtrar ou classificar objetos de um fluxo. Também é possível realizar agregações, como somar números, concatenar cadeias ou contar elementos. Além disso, os coletores dispõem de funções para transformar o conteúdo de um fluxo numa estrutura determinada. Dessa forma, por exemplo, é possível converter uma lista num mapa. Os agrupamentos ajudam a categorizar os elementos com determinadas propriedades ou condições. No entanto, a maior vantagem dos coletores de fluxo é que eles permitem processar dados em paralelo usando múltiplas threads (linhas). Isso permite que as operações, especialmente com grandes quantidades de dados, sejam realizadas de forma mais rápida e eficiente.
A sintaxe do Java Collect()
Este método recebe como argumento um coletor que indica como recolher e agregar os elementos do fluxo. Um coletor é uma interface que oferece diferentes métodos para agrupar os elementos do fluxo de uma forma específica, como uma lista, um conjunto ou um mapa.
Existem dois métodos no Java Collect:
- R collect(Fornecedor<R> fornecedor, BiConsumer<R, ? super T> acumulador, BiConsumer<R, R> combinador)
- <R, A> R collect(Collector<? super T, A, R> collector)
A primeira variante tem três funções como argumentos:
- supplier: cria um contentor que é utilizado para os resultados intermédios.
- acumulador: calcula o resultado final.
- combiner: combina os resultados de operações de fluxo paralelas.
Esses coletores predefinidos já estão incluídos na biblioteca padrão e podem ser importados e utilizados facilmente.
A segunda variante recebe um coletor como argumento e retorna o resultado.
- R: o tipo do resultado
- T: o tipo dos elementos do fluxo
- A: o tipo do acumulador que armazena o estado intermédio da operação do coletor
- coletor: realiza a operação de redução.
Com esta variante, os programadores têm a possibilidade de criar coletores personalizados que se ajustam especificamente às suas necessidades, proporcionando assim maior flexibilidade e controlo ao processo de redução.
Exemplos práticos do uso do Java Collect()
A seguir, apresentamos várias funções do método Stream.collect() para ilustrar a sua utilização. É recomendável que esteja familiarizado com os operadores Java antes de se aprofundar na estrutura de coleções.
Concatene uma lista de cadeias
Com Java Collect(), pode concatenar uma lista de cadeias para obter uma nova cadeia:
List<String> letters = List.of("a", "b", "c", "d", "e");
// without combiner function
StringBuilder result = letters.stream().collect(StringBuilder::new, (x, y) -> x.append(y),
(a, b) -> a.append(",").append(b));
System.out.println(result.toString());
// with combiner function
StringBuilder result1 = letters.parallelStream().collect(StringBuilder::new, (x, y) -> x.append(y),
(a, b) -> a.append(",").append(b));
System.out.println(result1.toString());JavaObtemos este resultado:
abcde
a, b, c, d, eJavaNo primeiro cálculo, existe apenas uma instância StringBuilder e nenhuma função combinadora foi utilizada. Portanto, o resultado é abcde.
No segundo resultado, vemos que a função combinadora fundiu as instâncias StringBuilder e as separou com uma vírgula.
Recolher os elementos de uma lista com toList()
Podemos selecionar determinados elementos de uma lista com a função filter() e armazená-los numa nova lista com toList().
List<Integer> numbers = List.of(1, 2, 3, 4, 5, 6, 7);
List<Integer> oddNumbers = numbers.stream().filter(x -> x % 2 != 0).collect(Collectors.toList());
System.out.println(oddNumbers);JavaA nova lista agora contém apenas números ímpares:
[1, 3, 5, 7]JavaRecolher os elementos de um conjunto com toSet()
Da mesma forma, podemos criar um novo conjunto (set) a partir dos elementos selecionados. A ordem num conjunto pode ser não ordenada.
List<Integer> numbers = List.of(1, 2, 3, 4, 5, 6, 7);
Set<Integer> evenNumbers = numbers.parallelStream().filter(x -> x % 2 == 0).collect(Collectors.toSet());
System.out.println(evenNumbers);JavaIsso nos dá este resultado:
[2, 4, 6]JavaRecolher elementos num mapa com toMap()
Um mapa em conjunto com Collect() de Java atribui um valor a cada chave.
List<Integer> numbers = List.of(1, 2, 3, 4, 5, 6, 7);
Map<Integer, String> mapEvenNumbers = numbers.parallelStream().filter(x -> x % 2 == 0)
.collect(Collectors.toMap(Function.identity(), x -> String.valueOf(x)));
System.out.println(mapEvenNumbers);JavaNo resultado, vemos que a entrada formada por números pares foi atribuída aos seus valores idênticos:
{2=2, 4=4, 6=6}JavaCombinar elementos de uma cadeia com joining()
O método joining() adiciona cada elemento do fluxo na ordem em que aparecem e utiliza um separador para separar os elementos. O separador é passado como argumento para joining(). Se nenhum separador for especificado, joining() utiliza a cadeia vazia "".
jshell> String result1 = Stream.of("a", "b", "c").collect(Collectors.joining());
jshell> String result2 = Stream.of("a", "b", "c").collect(Collectors.joining(",", "{", "}"));JavaOs resultados são os seguintes:
result1 ==> "abc"
result2 ==> "{a,b,c}"Java8957bde54f566ee4bf57dbb114b7c2f8