Os Stream Col­lec­tors são uma poderosa fun­ci­o­na­li­dade da API Java 8 Stream que permite recolher e processar dados de forma eficiente. Ex­pli­ca­mos a estrutura e as possíveis uti­li­za­çõ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 pro­ces­sa­dos 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 ca­na­li­za­ção. Estas são operações finais que recolhem e fundem os re­sul­ta­dos das etapas in­ter­mé­dias.

Os coletores podem ser usados, por exemplo, para filtrar ou clas­si­fi­car objetos de um fluxo. Também é possível realizar agre­ga­ções, como somar números, con­ca­te­nar cadeias ou contar elementos. Além disso, os coletores dispõem de funções para trans­for­mar o conteúdo de um fluxo numa estrutura de­ter­mi­nada. Dessa forma, por exemplo, é possível converter uma lista num mapa. Os agru­pa­men­tos ajudam a ca­te­go­ri­zar os elementos com de­ter­mi­na­das pro­pri­e­da­des 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, es­pe­ci­al­mente com grandes quan­ti­da­des de dados, sejam re­a­li­za­das 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 di­fe­ren­tes métodos para agrupar os elementos do fluxo de uma forma es­pe­cí­fica, como uma lista, um conjunto ou um mapa.

Existem dois métodos no Java Collect:

  1. R collect(For­ne­ce­dor<R> for­ne­ce­dor, Bi­Con­su­mer<R, ? super T> acu­mu­la­dor, Bi­Con­su­mer<R, R> com­bi­na­dor)
  2. <R, A> R collect(Collector<? super T, A, R> collector)

A primeira variante tem três funções como ar­gu­men­tos:

  • supplier: cria um contentor que é utilizado para os re­sul­ta­dos in­ter­mé­dios.
  • acu­mu­la­dor: calcula o resultado final.
  • combiner: combina os re­sul­ta­dos de operações de fluxo paralelas.

Esses coletores pre­de­fi­ni­dos já estão incluídos na bi­bli­o­teca padrão e podem ser im­por­ta­dos e uti­li­za­dos fa­cil­mente.

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 acu­mu­la­dor que armazena o estado in­ter­mé­dio da operação do coletor
  • coletor: realiza a operação de redução.

Com esta variante, os pro­gra­ma­do­res têm a pos­si­bi­li­dade de criar coletores per­so­na­li­za­dos que se ajustam es­pe­ci­fi­ca­mente às suas ne­ces­si­da­des, pro­por­ci­o­nando assim maior fle­xi­bi­li­dade e controlo ao processo de redução.

Exemplos práticos do uso do Java Collect()

A seguir, apre­sen­ta­mos várias funções do método Stream.collect() para ilustrar a sua uti­li­za­ção. É re­co­men­dá­vel que esteja fa­mi­li­a­ri­zado com os ope­ra­do­res Java antes de se apro­fun­dar na estrutura de coleções.

Concatene uma lista de cadeias

Com Java Collect(), pode con­ca­te­nar 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());
Java

Obtemos este resultado:

abcde
a, b, c, d, e
Java

No primeiro cálculo, existe apenas uma instância String­Buil­der e nenhuma função com­bi­na­dora foi utilizada. Portanto, o resultado é abcde.

No segundo resultado, vemos que a função com­bi­na­dora fundiu as ins­tân­cias String­Buil­der e as separou com uma vírgula.

Recolher os elementos de uma lista com toList()

Podemos se­le­ci­o­nar de­ter­mi­na­dos 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);
Java

A nova lista agora contém apenas números ímpares:

[1, 3, 5, 7]
Java

Recolher os elementos de um conjunto com toSet()

Da mesma forma, podemos criar um novo conjunto (set) a partir dos elementos se­le­ci­o­na­dos. 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);
Java

Isso nos dá este resultado:

[2, 4, 6]
Java

Recolher 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);
Java

No resultado, vemos que a entrada formada por números pares foi atribuída aos seus valores idênticos:

{2=2, 4=4, 6=6}
Java

Combinar 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 es­pe­ci­fi­cado, 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(",", "{", "}"));
Java

Os re­sul­ta­dos são os seguintes:

result1 ==> "abc"
result2 ==> "{a,b,c}"
Java

8957bde54f566ee4bf57dbb114b7c2f8

Ir para o menu principal