Ordenação de Dados com a Stream API em Java
Embora as coleções, que frequentemente servem como fonte para os streams, já possuam seus próprios métodos de ordenação, a Stream API também oferece essa funcionalidade diretamente no fluxo de dados. Essa capacidade é especialmente útil dentro de um pipeline de operações, permitindo que os dados sejam ordenados após uma filtragem ou mapeamento, antes de uma operação terminal.
Ordenação Natural com sorted()
Para uma ordenação simples em ordem ascendente (ou "natural"), é utilizado o método sorted()
.
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class Program {
public static void main(String[] args) {
List<String> phones = new ArrayList<>();
Collections.addAll(phones, "iPhone 15", "Pixel 8", "Galaxy S24",
"Xiaomi 14", "OnePlus 12", "Xperia 1 V");
phones.stream()
.filter(p -> p.length() < 10)
.sorted() // Ordenação em ordem alfabética natural
.forEach(s -> System.out.println(s));
}
}
Este método funciona bem para tipos que já possuem uma ordem definida, como números e strings. No entanto, ele depende da implementação da interface Comparable
pela classe dos elementos do stream.
Pixel 8 Xiaomi 14 iPhone 15
Ordenação Customizada com sorted(Comparator)
Quando a ordenação natural não é suficiente ou quando os objetos não implementam Comparable
, é necessário fornecer uma lógica de ordenação customizada. Isso é feito através do método sorted(Comparator)
, que aceita um Comparator
como argumento.
Vamos considerar a seguinte classe Phone
:
class Phone {
private String name;
private String company;
private int price;
// Construtor e Getters...
public Phone(String name, String comp, int price) {
this.name = name;
this.company = comp;
this.price = price;
}
public String getName() { return name; }
public String getCompany() { return company; }
public int getPrice() { return price; }
}
Existem três abordagens principais para fornecer um Comparator
.
1. Classe Separada (Abordagem Tradicional)
É possível criar uma classe que implementa Comparator
. Esta abordagem é verbosa e raramente usada em código moderno, mas é útil para entender o conceito.
class PhoneComparatorByName implements Comparator<Phone> {
@Override
public int compare(Phone a, Phone b) {
return a.getName().compareToIgnoreCase(b.getName());
}
}
// Uso: phoneStream.sorted(new PhoneComparatorByName())
2. Expressão Lambda (Abordagem Concisa)
Uma expressão lambda permite definir a lógica de comparação diretamente onde ela é necessária, tornando o código mais compacto.
// Ordenando por nome
phoneStream.sorted((p1, p2) -> p1.getName().compareToIgnoreCase(p2.getName()));
// Ordenando por preço
phoneStream.sorted((p1, p2) -> Integer.compare(p1.getPrice(), p2.getPrice()));
3. Métodos de Fábrica Comparator
(Abordagem Recomendada)
A forma mais moderna, legível e segura é usar os métodos estáticos da interface Comparator
, como comparing
, comparingInt
, etc., combinados com referências de método.
// Ordenando por nome (case-insensitive)
phoneStream.sorted(Comparator.comparing(Phone::getName, String.CASE_INSENSITIVE_ORDER));
// Ordenando por preço (ordem crescente)
phoneStream.sorted(Comparator.comparingInt(Phone::getPrice));
Esta abordagem é preferível por ser mais declarativa: ela diz "o quê" comparar, em vez de "como" comparar.
Ordenação por Múltiplos Critérios
Um cenário muito comum é a necessidade de ordenar por um segundo critério quando o primeiro resulta em empate. Isso é facilmente alcançado encadeando comparadores com o método thenComparing
.
Por exemplo, para ordenar os telefones por preço e, em caso de preços iguais, desempatar pelo nome em ordem alfabética:
Comparator<Phone> comparator = Comparator.comparingInt(Phone::getPrice)
.thenComparing(Phone::getName);
phoneStream.sorted(comparator);
Exemplo Completo e Prático
O código abaixo demonstra a ordenação por múltiplos critérios em um exemplo executável.
import java.util.Comparator;
import java.util.stream.Stream;
class Phone {
private String name;
private String company;
private int price;
public Phone(String name, String comp, int price) {
this.name = name;
this.company = comp;
this.price = price;
}
public String getName() { return name; }
public String getCompany() { return company; }
public int getPrice() { return price; }
}
public class SortingExample {
public static void main(String[] args) {
Stream.of(
new Phone("iPhone 15", "Apple", 999),
new Phone("Pixel 8", "Google", 799),
new Phone("iPhone 14", "Apple", 799), // Mesmo preço do Pixel 8
new Phone("Galaxy S24", "Samsung", 899),
new Phone("Xiaomi 14", "Xiaomi", 699)
)
// Ordena primeiro por preço (crescente), depois por nome (alfabético)
.sorted(Comparator.comparingInt(Phone::getPrice)
.thenComparing(Phone::getName))
.forEach(p -> System.out.printf("%s (%s) - %d USD\n",
p.getName(), p.getCompany(), p.getPrice()));
}
}
O resultado será:
Xiaomi 14 (Xiaomi) - 699 USD iPhone 14 (Apple) - 799 USD Pixel 8 (Google) - 799 USD Galaxy S24 (Samsung) - 899 USD iPhone 15 (Apple) - 999 USD
Note que o iPhone 14
e o Pixel 8
, ambos com o mesmo preço, foram desempatados alfabeticamente pelo nome.
Resumo
- O método
sorted()
ordena elementos em sua ordem natural (requerComparable
). - O método
sorted(Comparator)
permite uma ordenação customizada. - A forma moderna e recomendada de criar um
Comparator
é com os métodos de fábricaComparator.comparing()
oucomparingInt()
, utilizando referências de método (ex:Phone::getPrice
). - Para ordenar por múltiplos critérios, encadeie comparadores com o método
thenComparing
.