Métodos de Agregação na Stream API: Agregando Resultados
Os métodos de agregação (ou de redução) são métodos terminais que processam todos os elementos de um stream para produzir um único resultado. Esse resultado pode ser um valor primitivo (como uma contagem), um objeto específico do stream (como o elemento de menor valor) ou um booleano.
Contagem de Elementos: count
O método count()
é um método terminal simples que retorna a quantidade de elementos no stream como um long
.
import java.util.List;
public class Program {
public static void main(String[] args) {
List<String> names = List.of("Tom", "Sam", "Bob", "Alice");
// Conta todos os elementos no stream
long totalCount = names.stream().count();
System.out.println(totalCount); // Saída: 4
// Conta apenas os elementos que atendem a uma condição
long filteredCount = names.stream().filter(n -> n.length() <= 3).count();
System.out.println(filteredCount); // Saída: 3
}
}
Buscando Elementos: findFirst e findAny
Os métodos findFirst()
e findAny()
são usados para extrair um elemento do stream. Ambos retornam o resultado encapsulado em um objeto Optional
, pois o stream pode estar vazio.
findFirst()
: Retorna o primeiro elemento do stream. É um método determinístico.findAny()
: Retorna qualquer elemento do stream. Em streams sequenciais, geralmente retorna o primeiro, mas em streams paralelos, pode retornar qualquer elemento que seja processado primeiro, otimizando a performance.
import java.util.List;
import java.util.Optional;
public class Program {
public static void main(String[] args) {
List<String> names = List.of("Tom", "Sam", "Bob", "Alice");
Optional<String> first = names.stream().findFirst();
first.ifPresent(name -> System.out.println("Primeiro elemento: " + name)); // Saída: Tom
Optional<String> any = names.stream().findAny();
any.ifPresent(name -> System.out.println("Qualquer elemento: " + name)); // Saída: Tom
}
}
Nota sobre Optional
: É uma boa prática usar métodos como ifPresent()
ou orElse()
para lidar com o valor de um Optional
de forma segura, evitando NullPointerException
. Chamar get()
diretamente em um Optional
vazio resultará em uma exceção.
Verificação de Condições: allMatch
, anyMatch
, noneMatch
Este grupo de métodos terminais de curto-circuito verifica se os elementos do stream correspondem a um predicado e retorna um boolean
.
anyMatch(predicate)
: Retornatrue
se pelo menos um elemento corresponder à condição. Para ao encontrar o primeiro.allMatch(predicate)
: Retornatrue
se todos os elementos corresponderem à condição. Para ao encontrar o primeiro que não corresponde.noneMatch(predicate)
: Retornatrue
se nenhum elemento corresponder à condição. Para ao encontrar o primeiro que corresponde.
import java.util.List;
public class Program {
public static void main(String[] args) {
List<String> names = List.of("Tom", "Sam", "Bob", "Alice");
// Existe alguma string com mais de 3 caracteres?
boolean any = names.stream().anyMatch(s -> s.length() > 3);
System.out.println("anyMatch: " + any); // true
// Todas as strings têm exatamente 3 caracteres?
boolean all = names.stream().allMatch(s -> s.length() == 3);
System.out.println("allMatch: " + all); // false
// Nenhuma string é igual a "Bill"?
boolean none = names.stream().noneMatch(s -> s.equals("Bill"));
System.out.println("noneMatch: " + none); // true
}
}
Encontrando Mínimos e Máximos: min e max
Os métodos min()
e max()
retornam, respectivamente, o menor e o maior elemento do stream de acordo com um critério de comparação fornecido. Como o stream pode conter qualquer tipo de objeto, é necessário passar um Comparator
para definir como os elementos devem ser comparados.
Optional<T> min(Comparator<? super T> comparator)
Optional<T> max(Comparator<? super T> comparator)
Ambos retornam um Optional
, pois o stream pode estar vazio.
Para um stream de números, a comparação é direta:
import java.util.List;
import java.util.Optional;
public class Program {
public static void main(String[] args) {
List<Integer> numbers = List.of(1, 2, 3, 4, 5, 6, 7, 8, 9);
Optional<Integer> min = numbers.stream().min(Integer::compare);
Optional<Integer> max = numbers.stream().max(Integer::compare);
min.ifPresent(val -> System.out.println("Min: " + val)); // Saída: 1
max.ifPresent(val -> System.out.println("Max: " + val)); // Saída: 9
}
}
Aqui, Integer::compare
é uma referência de método que atua como o Comparator
.
Para objetos mais complexos, a abordagem moderna é usar os métodos de fábrica da classe Comparator
.
import java.util.Comparator;
import java.util.List;
class Phone {
private String name;
private int price;
public Phone(String name, int price) { /* ... */ }
public String getName() { return name; }
public int getPrice() { return price;}
}
public class Program {
public static void main(String[] args) {
List<Phone> phones = List.of(
new Phone("iPhone 15", 999),
new Phone("Nokia G42", 250),
new Phone("Samsung Galaxy S24", 899),
new Phone("Pixel 8", 799)
);
// Encontra o telefone mais barato e o mais caro usando Comparator.comparingInt
phones.stream()
.min(Comparator.comparingInt(Phone::getPrice))
.ifPresent(p -> System.out.printf("MIN: %s, Price: %d\n", p.getName(), p.getPrice()));
phones.stream()
.max(Comparator.comparingInt(Phone::getPrice))
.ifPresent(p -> System.out.printf("MAX: %s, Price: %d\n", p.getName(), p.getPrice()));
}
}
Neste exemplo, Comparator.comparingInt(Phone::getPrice)
cria um Comparator
que compara os objetos Phone
com base no valor retornado pelo método getPrice
. Esta é a forma preferida e mais legível para realizar comparações.
A saída será:
MIN: Nokia G42, Price: 250 MAX: iPhone 15, Price: 999
Resumo
count()
: Retorna a quantidade de elementos no stream.findFirst()
/findAny()
: Retornam um elemento do stream encapsulado em umOptional
.anyMatch()
/allMatch()
/noneMatch()
: Verificam se os elementos atendem a uma condição e retornam umboolean
. São métodos de curto-circuito.min(c)
/max(c)
: Retornam o menor ou o maior elemento de acordo com umComparator
fornecido, também em umOptional
.