1. Contagem de elementos: count()
Quando você trabalha com coleções de números ou objetos, quase sempre surge a tarefa de contar algo: quantidade de elementos, soma, média, máximo ou mínimo. Por exemplo, quantidade de funcionários na empresa, soma de todos os salários, média de idade dos estudantes, o produto mais caro, o maior pedido. Com o Stream API, tudo ficou mais simples e expressivo.
Como funciona
O método count() é um operador terminal do Stream API que retorna a quantidade de elementos no stream.
long count = employees.stream().count();
Se você precisa contar apenas os elementos que atendem a uma condição — use o filtro filter(...):
long richCount = employees.stream()
.filter(e -> e.getSalary() > 100_000)
.count();
Exemplo para o nosso aplicativo:
Suponha que haja uma lista de produtos:
List<Product> products = List.of(
new Product("Leite", 80),
new Product("Queijo", 250),
new Product("Pão", 40)
);
Vamos contar quantos produtos custam mais de 100:
long expensiveCount = products.stream()
.filter(p -> p.getPrice() > 100)
.count();
System.out.println("Produtos caros: " + expensiveCount);
O que é Optional
No Java há um contêiner especial — Optional. É um objeto wrapper que pode conter um valor ou estar vazio. Ele é usado nos casos em que pode não haver resultado, e isso precisa ser indicado explicitamente. Por exemplo, os métodos min(), max() e average() podem retornar resultado vazio se a coleção estiver vazia. Em vez de null é retornado um Optional.
Principais métodos de Optional
- isPresent() / isEmpty() — verificar se há valor.
- orElse(valor) — retorna o valor ou o padrão, se estiver vazio.
- orElseThrow() — lança uma exceção, se estiver vazio.
- ifPresent(...) — executa uma ação apenas quando houver valor.
Exemplo:
OptionalInt min = products.stream()
.mapToInt(Product::getPrice)
.min();
if (min.isPresent()) {
System.out.println("Preço mínimo: " + min.getAsInt());
} else {
System.out.println("Lista vazia");
}
Ou de forma mais curta:
int minValue = min.orElse(-1);
System.out.println("Preço mínimo: " + minValue);
Essa abordagem ajuda a evitar NullPointerException e torna o código mais seguro.
2. Soma, média, mínimo e máximo para dados numéricos
Streams primitivos: IntStream, DoubleStream, LongStream
Para dados numéricos o Stream API oferece streams especiais: IntStream, DoubleStream, LongStream. Eles permitem calcular facilmente soma, média, mínimo e máximo.
Converter para um stream numérico
Para obter um stream numérico a partir de um stream de objetos, use os métodos mapToInt, mapToDouble, mapToLong.
int sum = products.stream()
.mapToInt(Product::getPrice)
.sum();
System.out.println("Soma total dos preços: " + sum);
Métodos
- sum() — soma de todos os elementos.
- average() — média aritmética (retorna OptionalDouble).
- min(), max() — mínimo e máximo (retornam OptionalInt/OptionalDouble/OptionalLong).
Exemplos:
// Soma de todos os preços
int total = products.stream()
.mapToInt(Product::getPrice)
.sum();
// Preço médio
OptionalDouble avg = products.stream()
.mapToInt(Product::getPrice)
.average();
// Preço mínimo e máximo
OptionalInt min = products.stream()
.mapToInt(Product::getPrice)
.min();
OptionalInt max = products.stream()
.mapToInt(Product::getPrice)
.max();
Como obter o valor de um Optional
double average = avg.orElse(0.0); // 0.0 se a coleção estiver vazia
int minValue = min.orElse(-1); // -1 se a coleção estiver vazia
int maxValue = max.orElse(-1); // -1 se a coleção estiver vazia
Piada de programador: lista vazia — não é erro, é apenas uma situação em que você não tem dados. Mas se tentar pegar um valor de um Optional vazio com o método getAsInt() — você terá um NoSuchElementException. Sempre use orElse ou isPresent()!
3. Agregação de objetos: Collectors.summingInt, averagingInt, maxBy, minBy
Se você tem um stream de objetos e quer agregar valores por algum critério, use coletores especiais da classe Collectors.
summingInt, averagingInt
int totalSalary = employees.stream()
.collect(Collectors.summingInt(Employee::getSalary));
double avgSalary = employees.stream()
.collect(Collectors.averagingInt(Employee::getSalary));
minBy, maxBy
Para encontrar o objeto com o maior/menor valor de um campo:
Optional<Employee> richest = employees.stream()
.collect(Collectors.maxBy(Comparator.comparingInt(Employee::getSalary)));
Optional<Employee> poorest = employees.stream()
.collect(Collectors.minBy(Comparator.comparingInt(Employee::getSalary)));
Importante: o resultado é um Optional, pois a coleção pode estar vazia.
Exemplo
Vamos criar a classe Product:
public class Product {
private final String name;
private final int price;
public Product(String name, int price) {
this.name = name;
this.price = price;
}
public String getName() { return name; }
public int getPrice() { return price; }
}
Vamos criar a lista de produtos:
List<Product> products = List.of(
new Product("Leite", 80),
new Product("Queijo", 250),
new Product("Pão", 40)
);
Vamos encontrar o produto mais caro:
Optional<Product> maxProduct = products.stream()
.collect(Collectors.maxBy(Comparator.comparingInt(Product::getPrice)));
maxProduct.ifPresent(p -> System.out.println("Produto mais caro: " + p.getName()));
4. Combinando com agrupamento
Métodos de agregação frequentemente são combinados com agrupamento. Por exemplo, é possível encontrar o preço médio dos produtos pela primeira letra do nome:
Map<Character, Double> avgPriceByLetter = products.stream()
.collect(Collectors.groupingBy(
p -> p.getName().charAt(0),
Collectors.averagingInt(Product::getPrice)
));
System.out.println(avgPriceByLetter);
Resultado:
{L=80.0, Q=250.0, P=40.0}
5. Exemplos do mundo real
Exemplo 1: Alunos e notas
Lista de alunos e suas notas:
public class Student {
private final String name;
private final int score;
public Student(String name, int score) {
this.name = name;
this.score = score;
}
public String getName() {
return name;
}
public int getScore() {
return score;
}
}
List<Student> students = List.of(
new Student("Alisa", 90),
new Student("Bob", 75),
new Student("Vasya", 100)
);
Quantos alunos têm nota acima de 80?
long count = students.stream()
.filter(s -> s.getScore() > 80)
.count();
System.out.println("Alunos com nota > 80: " + count);
Média das notas:
double avg = students.stream()
.mapToInt(Student::getScore)
.average()
.orElse(0.0);
System.out.println("Média das notas: " + avg);
Aluno com melhor nota:
students.stream()
.max(Comparator.comparingInt(Student::getScore))
.ifPresent(s -> System.out.println("Melhor aluno: " + s.getName()));
Exemplo 2: Quantidade de produtos únicos
long uniqueCount = products.stream()
.map(Product::getName)
.distinct()
.count();
System.out.println("Produtos únicos: " + uniqueCount);
6. Detalhes úteis
Esquema visual: como funcionam os métodos de agregação
Coleção de objetos
│
▼
stream()
│
▼
mapToInt/Double/Long (se necessário)
│
▼
sum() / average() / min() / max() / count()
│
▼
Resultado (int, double, long, Optional)
Tratando Optional: como extrair valores com segurança
Por que os métodos retornam Optional?
Se a coleção estiver vazia (por exemplo, você procura o máximo em uma lista vazia), é retornado um Optional vazio. Se você chamar diretamente getAsInt()/get(), uma exceção será lançada.
Como fazer corretamente:
- Usar orElse(valor padrão)
- Usar ifPresent(...) para agir apenas quando houver valor
- Usar orElseThrow(...) para lançar uma exceção explicitamente
Exemplo:
OptionalDouble avg = products.stream()
.mapToInt(Product::getPrice)
.average();
System.out.println("Preço médio: " + avg.orElse(0.0));
Quando usar streams primitivos e quando usar Collectors
- Se você precisa apenas calcular soma/média/mínimo/máximo de um campo numérico — use mapToInt e o método correspondente (sum, average, min, max).
- Se você quer agregar objetos (por exemplo, encontrar o objeto com o maior valor em um campo), use os coletores maxBy/minBy.
- Se precisar agrupar e calcular agregados por grupos — use a combinação de groupingBy com coletores agregadores.
7. Erros comuns ao trabalhar com métodos de agregação
Erro nº 1: Optional não tratado. Com frequência iniciantes esquecem que min, max, average retornam um Optional. Como resultado, ao tentar obter o valor de um contêiner vazio ocorre um NoSuchElementException. Sempre use orElse, orElseThrow ou ifPresent.
Erro nº 2: Uso de stream normal em vez de mapToInt/mapToDouble. Se você escrever stream().sum(), o compilador dirá: “Esse método não existe!”. Para soma, média, mínimo e máximo são necessários streams primitivos.
Erro nº 3: Incompatibilidade de tipos na comparação. Ao buscar máximo/mínimo entre objetos, use o comparador correto: Comparator.comparingInt(Employee::getSalary). Caso contrário, você pode obter um erro de compilação ou um resultado incorreto.
Erro nº 4: Comparar objetos com null. Se a coleção pode conter elementos null, a agregação pode lançar NullPointerException. É melhor filtrar esses elementos previamente ou usar Comparator.nullsLast(...)/nullsFirst(...).
Erro nº 5: Usar sum/average/min/max com coleções vazias sem considerar o resultado. Se a coleção estiver vazia, o método sum() retornará 0, e average()/min()/max() — um Optional vazio. Não se esqueça de tratar esse caso.
GO TO FULL VERSION