Atualizado: 21/09/2025

Este conteúdo é original e não foi gerado por inteligência artificial.

Agrupando Dados com Collectors.groupingBy em Java

Uma das operações de coleta mais poderosas da Stream API é o agrupamento de dados. Para quem está familiarizado com SQL, o coletor Collectors.groupingBy() é o análogo funcional da cláusula GROUP BY. Ele permite segmentar os elementos de um stream em um Map com base em um critério específico e, opcionalmente, realizar agregações dentro de cada grupo.

Para os exemplos a seguir, vamos utilizar um record para representar um telefone:

record Phone(String name, String company, int price) {}

Agrupamento Simples

A forma mais básica de groupingBy aceita uma função classificadora (classifier function). Esta função é aplicada a cada elemento do stream, e o valor que ela retorna é usado como a chave no Map resultante. Por padrão, os valores associados a cada chave são os elementos do stream, coletados em uma List.

Vamos agrupar uma lista de telefones por fabricante (company):

import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;

record Phone(String name, String company, int price) {}

public class GroupingExample {
    public static void main(String[] args) {
        Stream<Phone> phoneStream = Stream.of(
            new Phone("iPhone 15", "Apple", 999),
            new Phone("Pixel 8", "Google", 799),
            new Phone("iPhone 14", "Apple", 899),
            new Phone("Galaxy S24", "Samsung", 899),
            new Phone("Galaxy Z Fold5", "Samsung", 1799)
        );

        Map<String, List<Phone>> phonesByCompany = phoneStream.collect(
            Collectors.groupingBy(Phone::company)
        );

        for (Map.Entry<String, List<Phone>> item : phonesByCompany.entrySet()) {
            System.out.println(item.getKey());
            for (Phone phone : item.getValue()) {
                System.out.println("  - " + phone.name());
            }
            System.out.println();
        }
    }
}

A expressão Phone::company é a função classificadora. O resultado é um Map<String, List<Phone>>.

Saída:

Google
  - Pixel 8

Apple
  - iPhone 15
  - iPhone 14

Samsung
  - Galaxy S24
  - Galaxy Z Fold5

Coletores Aninhados: Agregando Dados nos Grupos

Frequentemente, não queremos apenas a lista de objetos, mas sim realizar uma agregação dentro de cada grupo (contar, somar, etc.). Para isso, groupingBy aceita um segundo argumento: um coletor aninhado (downstream collector).

Contando Elementos em Grupos: Collectors.counting()

Para saber quantos telefones cada empresa possui na lista (análogo a COUNT(*) em SQL).

// ... (imports e record Phone)
public class CountingExample {
    public static void main(String[] args) {
        Stream<Phone> phoneStream = Stream.of(/* ... mesma lista de telefones ... */);
        Map<String, Long> phonesCount = phoneStream.collect(
            Collectors.groupingBy(Phone::company, Collectors.counting())
        );

        for (Map.Entry<String, Long> item : phonesCount.entrySet()) {
            System.out.println(item.getKey() + " - " + item.getValue());
        }
    }
}

Saída:

Google - 1
Apple - 2
Samsung - 2

Somando Valores em Grupos: Collectors.summingInt()

Para calcular o valor total do estoque por empresa (análogo a SUM(price)).

// ... (imports e record Phone)
public class SummingExample {
    public static void main(String[] args) {
        Stream<Phone> phoneStream = Stream.of(/* ... mesma lista de telefones ... */);
        Map<String, Integer> totalValueByCompany = phoneStream.collect(
            Collectors.groupingBy(Phone::company, Collectors.summingInt(Phone::price))
        );

        for (Map.Entry<String, Integer> item : totalValueByCompany.entrySet()) {
            System.out.println(item.getKey() + " - " + item.getValue());
        }
    }
}

Saída:

Google - 799
Apple - 1898
Samsung - 2698

Encontrando Mínimos e Máximos: minBy() e maxBy()

Para encontrar o telefone mais barato de cada empresa, usamos minBy, que requer um Comparator.

// ... (imports e record Phone)
import java.util.Comparator;
import java.util.Optional;

public class MinByExample {
    public static void main(String[] args) {
        Stream<Phone> phoneStream = Stream.of(/* ... mesma lista de telefones ... */);
        Map<String, Optional<Phone>> cheapestPhoneByCompany = phoneStream.collect(
            Collectors.groupingBy(Phone::company,
                Collectors.minBy(Comparator.comparing(Phone::price)))
        );

        for (Map.Entry<String, Optional<Phone>> item : cheapestPhoneByCompany.entrySet()) {
            System.out.println(item.getKey() + " - " + item.getValue().get().name());
        }
    }
}

Saída:

Google - Pixel 8
Apple - iPhone 14
Samsung - Galaxy S24

Obtendo Estatísticas Completas: summarizingInt()

O coletor summarizingInt() calcula a contagem, soma, mínimo, máximo e a média de uma só vez.

// ... (imports e record Phone)
import java.util.IntSummaryStatistics;

public class SummarizingExample {
    public static void main(String[] args) {
        Stream<Phone> phoneStream = Stream.of(/* ... mesma lista de telefones ... */);
        Map<String, IntSummaryStatistics> priceSummary = phoneStream.collect(
            Collectors.groupingBy(Phone::company,
                Collectors.summarizingInt(Phone::price))
        );

        for (Map.Entry<String, IntSummaryStatistics> item : priceSummary.entrySet()) {
            System.out.println(item.getKey() + " - Média: " + item.getValue().getAverage());
        }
    }
}

Saída:

Google - Média: 799.0
Apple - Média: 949.0
Samsung - Média: 1349.0

Mapeando os Valores do Grupo: mapping()

Para agrupar por empresa, mas coletar apenas os nomes dos telefones em uma lista.

// ... (imports e record Phone)
public class MappingExample {
    public static void main(String[] args) {
        Stream<Phone> phoneStream = Stream.of(/* ... mesma lista de telefones ... */);
        Map<String, List<String>> phoneNamesByCompany = phoneStream.collect(
            Collectors.groupingBy(Phone::company,
                Collectors.mapping(Phone::name, Collectors.toList()))
        );

        for (Map.Entry<String, List<String>> item : phoneNamesByCompany.entrySet()) {
            System.out.println(item.getKey());
            for (String name : item.getValue()) {
                System.out.println("  - " + name);
            }
        }
    }
}

Saída:

Google
  - Pixel 8
Apple
  - iPhone 15
  - iPhone 14
Samsung
  - Galaxy S24
  - Galaxy Z Fold5

Particionando: Um Caso Especial de Agrupamento

O Collectors.partitioningBy() divide o stream em exatamente duas coleções (verdadeiro/falso) com base em um Predicate.

// ... (imports e record Phone)
public class PartitioningExample {
    public static void main(String[] args) {
        Stream<Phone> phoneStream = Stream.of(/* ... mesma lista de telefones ... */);
        Map<Boolean, List<Phone>> phonesByPrice = phoneStream.collect(
            Collectors.partitioningBy(p -> p.price() > 900)
        );

        for (Map.Entry<Boolean, List<Phone>> item : phonesByPrice.entrySet()) {
            System.out.println("Preço > 900: " + item.getKey());
            for (Phone phone : item.getValue()) {
                System.out.println("  - " + phone.name());
            }
            System.out.println();
        }
    }
}

Resumo

  • Collectors.groupingBy(classifier) agrupa elementos de um stream em um Map, onde a chave é o resultado do classificador e o valor é uma List dos elementos.
  • groupingBy pode aceitar um segundo coletor (um coletor aninhado) para processar os valores de cada grupo (ex: counting(), summingInt(), mapping()).
  • Collectors.partitioningBy(predicate) é uma forma otimizada de agrupamento que sempre divide o stream em duas partições (verdadeiro/falso).
Política de Privacidade

Copyright © www.programicio.com Todos os direitos reservados

É proibida a reprodução do conteúdo desta página sem autorização prévia do autor.

Contato: programicio@gmail.com