CodeGym /Cursos /JAVA 25 SELF /Métodos sum, count, average, max, min no Stream API

Métodos sum, count, average, max, min no Stream API

JAVA 25 SELF
Nível 31 , Lição 0
Disponível

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.

Comentários
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION