Guia para o agrupamento do Java 8
*1. Introdução *
Neste artigo, veremos como o coletor groupingBy funciona usando vários exemplos.
Para entender o material abordado neste artigo, é necessário um conhecimento básico dos recursos do Java 8. Você pode dar uma olhada no link:/java-8-streams-Introduction [introdução ao Java 8 Streams] e no link:/java-8-collectors [guia para os coletores do Java 8].
Leitura adicional:
https://www..com/java-stream-immutable-collection [Colete um fluxo Java em uma coleção imutável]
Aprenda a coletar Java Streams em coleções imutáveis.
https://www..com/java-stream-immutable-collection [Leia mais] →
https://www..com/java-collectors-tomap [Java 8 Collectors toMap]
Aprenda a usar o método toMap () da classe Collectors.
https://www..com/java-collectors-tomap [Leia mais] →
===* 2. GroupingBy Collectors *
A API Java 8 Stream permite processar coleções de dados de maneira declarativa.
Os métodos estáticos de fábrica Collectors.groupingBy () _ e _Collectors.groupingByConcurrent () _ nos fornecem uma funcionalidade semelhante à cláusula G _GROUP BY' na linguagem SQL. Eles são usados para agrupar objetos por alguma propriedade e armazenar resultados em uma instância Map.
Os métodos sobrecarregados de groupingBy:
-
[.s1] #Com uma função de classificação como parâmetro do método: #
static <T,K> Collector<T,?,Map<K,List<T>>>
groupingBy(Function<? super T,? extends K> classifier)
-
Com uma função de classificação e um segundo coletor como parâmetros do método:
static <T,K,A,D> Collector<T,?,Map<K,D>>
groupingBy(Function<? super T,? extends K> classifier,
Collector<? super T,A,D> downstream)
*Com uma função de classificação, um método de fornecedor (que fornece a implementação _Map_ que conterá o resultado final) e um segundo coletor como parâmetros do método:
static <T,K,D,A,M extends Map<K,D>> Collector<T,?,M>
groupingBy(Function<? super T,? extends K> classifier,
Supplier<M> mapFactory, Collector<? super T,A,D> downstream)
====* 2.1 Exemplo de configuração de código *
Para demonstrar o uso de groupingBy (), vamos definir uma classe BlogPost (usaremos um fluxo de objetos BlogPost):
class BlogPost {
String title;
String author;
BlogPostType type;
int likes;
}
O BlogPostType:
enum BlogPostType {
NEWS,
REVIEW,
GUIDE
}
A List dos objetos BlogPost:
List<BlogPost> posts = Arrays.asList( ... );
Vamos também definir uma classe Tuple que será usada para agrupar postagens pela combinação dos atributos type e author:
class Tuple {
BlogPostType type;
String author;
}
====* 2.2 Agrupamento simples por uma única coluna *
Vamos começar com o método groupingBy mais simples, que usa apenas uma função de classificação como parâmetro. Uma função de classificação é aplicada a cada elemento do fluxo. O valor retornado pela função é usado como uma chave para o mapa que obtemos do coletor groupingBy.
Para agrupar as postagens do blog na lista de postagens do blog por seu type:
Map<BlogPostType, List<BlogPost>> postsPerType = posts.stream()
.collect(groupingBy(BlogPost::getType));
====* 2.3 Agrupando com um tipo de chave Map complexo *
A função de classificação não se limita a retornar apenas um valor escalar ou String. A chave do mapa resultante pode ser qualquer objeto, desde que tenhamos certeza de que implementamos os métodos equals e hashcode necessários.
Para agrupar pelas postagens do blog na lista pelo type e author combinados em uma instância Tuple:
Map<Tuple, List<BlogPost>> postsPerTypeAndAuthor = posts.stream()
.collect(groupingBy(post -> new Tuple(post.getType(), post.getAuthor())));
====* 2.4. Modificando o tipo de valor Map retornado *
A segunda sobrecarga de groupingBy leva um segundo coletor adicional (coletor a jusante), que é aplicado aos resultados do primeiro coletor.
Quando especificamos apenas uma função de classificação e não um coletor a jusante, o coletor _toList () _ é usado nos bastidores.
Vamos usar o coletor toSet () _ como coletor downstream e obter um _Set de postagens do blog (em vez de uma List):
Map<BlogPostType, Set<BlogPost>> postsPerType = posts.stream()
.collect(groupingBy(BlogPost::getType, toSet()));
====* 2.5 Agrupando por vários campos *
Uma aplicação diferente do coletor a jusante é fazer um agrupamento secundário pelos resultados do primeiro grupo por.
Para agrupar a List de BlogPost__s primeiro por _author e depois por type:
Map<String, Map<BlogPostType, List>> map = posts.stream()
.collect(groupingBy(BlogPost::getAuthor, groupingBy(BlogPost::getType)));
====* 2.6 Obtendo a média dos resultados agrupados *
Usando o coletor a jusante, podemos aplicar funções de agregação nos resultados da função de classificação.
Para encontrar o número médio de likes para cada postagem do blog type:
Map<BlogPostType, Double> averageLikesPerType = posts.stream()
.collect(groupingBy(BlogPost::getType, averagingInt(BlogPost::getLikes)));
====* 2.7 Obtendo a soma dos resultados agrupados *
Para calcular a soma total de likes para cada type:
Map<BlogPostType, Integer> likesPerType = posts.stream()
.collect(groupingBy(BlogPost::getType, summingInt(BlogPost::getLikes)));
====* 2.8. Obtendo o máximo ou o mínimo dos resultados agrupados *
Outra agregação que podemos executar é obter a postagem do blog com o número máximo de curtidas:
Map<BlogPostType, Optional<BlogPost>> maxLikesPerPostType = posts.stream()
.collect(groupingBy(BlogPost::getType,
maxBy(comparingInt(BlogPost::getLikes))));
Da mesma forma, podemos aplicar o coletor downstream minBy para obter a postagem do blog com o número mínimo de likes.
Observe que os coletores maxBy e minBy levam em conta a possibilidade de que a coleção à qual ela é aplicada possa estar vazia. É por isso que o tipo de valor no mapa é _Optional <BlogPost> _.
====* 2,9. Obtendo um resumo para um atributo de resultados agrupados *
A API Collectors oferece um coletor de resumo que pode ser usado nos casos em que precisamos calcular a contagem, soma, mínimo, máximo e média de um atributo numérico ao mesmo tempo.
Vamos calcular um resumo para o atributo de curtidas das postagens do blog para cada tipo diferente:
Map<BlogPostType, IntSummaryStatistics> likeStatisticsPerType = posts.stream()
.collect(groupingBy(BlogPost::getType,
summarizingInt(BlogPost::getLikes)));
O objeto IntSummaryStatistics para cada tipo contém os valores count, sum, average, min e max para o atributo likes. Existem objetos de resumo adicionais para valores duplos e longos.
====* 2.10 Mapeando resultados agrupados para um tipo diferente *
Agregações mais complexas podem ser obtidas aplicando um coletor mapping downstream aos resultados da função de classificação.
Vamos obter uma concatenação dos titless das postagens para cada postagem do blog type:
Map<BlogPostType, String> postsPerType = posts.stream()
.collect(groupingBy(BlogPost::getType,
mapping(BlogPost::getTitle, joining(", ", "Post titles: [", "]"))));
O que fizemos aqui é mapear cada instância BlogPost para seu title e, em seguida, reduzir o fluxo de títulos de post para uma String concatenada. Neste exemplo, o tipo do valor Map também é diferente do tipo List padrão.
====* 2.11 Modificando o tipo de retorno Map *
Ao usar o coletor groupingBy, não podemos fazer suposições sobre o tipo do Map retornado. Se quisermos ser específicos sobre o tipo de Map que queremos obter do grupo, podemos usar a terceira variação do método groupingBy que nos permite alterar o tipo de Map passando uma função de fornecedor Map.
Vamos recuperar um EnumMap passando uma função de fornecedor EnumMap para o método groupingBy:
EnumMap<BlogPostType, List<BlogPost>> postsPerType = posts.stream()
.collect(groupingBy(BlogPost::getType,
() -> new EnumMap<>(BlogPostType.class), toList()));
===* 3. Agrupamento simultâneo por coletor *
Semelhante ao groupingBy, existe o coletor groupingByConcurrent, que utiliza arquiteturas com vários núcleos. Esse coletor possui três métodos sobrecarregados que usam exatamente os mesmos argumentos que os respectivos métodos sobrecarregados do coletor groupingBy. O tipo de retorno do coletor groupingByConcurrent, no entanto, deve ser uma instância da classe ConcurrentHashMap ou uma subclasse dela.
Para executar uma operação de agrupamento simultaneamente, o fluxo precisa ser paralelo:
ConcurrentMap<BlogPostType, List<BlogPost>> postsPerType = posts.parallelStream()
.collect(groupingByConcurrent(BlogPost::getType));
Se optarmos por passar uma função de fornecedor Map para o coletor groupingByConcurrent, precisaremos garantir que a função retorne um ConcurrentHashMap ou uma subclasse dela.
===* 4. Adições ao Java 9 *
O Java 9 trouxe dois novos coletores que funcionam bem com groupingBy - mais informações sobre isso podem ser encontradas no link:/java9-stream-collectors [aqui].
===* 5. Conclusão*
Neste artigo, vimos vários exemplos do uso do coletor groupingBy oferecido pela API Java 8 Collectors.
Vimos como groupingBy pode ser usado para classificar um fluxo de elementos com base em um de seus atributos e como os resultados da classificação podem ser coletados, alterados e reduzidos a contêineres finais.
A implementação completa dos exemplos deste artigo pode ser encontrada em o projeto GitHub.