Como ordenar uma List em Java: list.sort, Comparator, múltiplas condições e tratamento de null

目次

1. O Que Você Vai Aprender Neste Artigo (O Caminho Mais Curto para Ordenar uma Lista Java)

Ao trabalhar com Java, é extremamente comum encontrar situações em que você precisa ordenar uma List.
Ao mesmo tempo, muitos desenvolvedores — especialmente iniciantes — ficam confusos com perguntas como:

  • Qual método devo usar para ordenar uma List?
  • Qual a diferença entre list.sort() e Collections.sort()?
  • Como ordeno uma List de objetos em vez de valores simples?

Este artigo foi criado para responder a essas perguntas de forma clara e prática, começando pela conclusão e, em seguida, explicando gradualmente os detalhes e casos de uso reais.

1.1 A Conclusão: Este É o Único Padrão que Você Precisa Lembrar

Se você quer a maneira mais curta e padrão de ordenar uma List em Java, é esta:

list.sort(Comparator.naturalOrder());

Isso ordena a List em ordem crescente (do menor para o maior, de A a Z, do mais antigo ao mais recente).

Se você quiser ordem decrescente, use isto em vez disso:

list.sort(Comparator.reverseOrder());

Com apenas essas duas linhas, você já pode ordenar Lists de:

  • Integer
  • String
  • LocalDate
  • e a maioria dos outros tipos comuns

Para muitos casos do dia a dia, isso é tudo o que você precisa.

1.2 Ordenando uma List de Objetos: Especifique a Chave de Ordenação

Em aplicações reais, as Lists geralmente contêm objetos, não valores simples.

Por exemplo:

class Person {
    private String name;
    private int age;

    // getters omitted
}

Para ordenar um List<Person> por idade, escreva:

list.sort(Comparator.comparing(Person::getAge));

Essa única linha significa:

  • Extrair age de cada Person
  • Comparar esses valores
  • Ordenar a List em ordem crescente

Você não precisa implementar a lógica de comparação manualmente.
Basta lembrar deste padrão:

Comparator.comparing(whatToCompare)

1.3 O Que Este Artigo Cobre

Este artigo explica a ordenação de Lists em Java desde o básico até o uso prático, incluindo:

  • A diferença entre list.sort() e Collections.sort()
  • Como o Comparator funciona em termos simples
  • Ordenação com múltiplas condições (por exemplo: idade → nome)
  • Mistura de ordem crescente e decrescente
  • Como lidar com valores null de forma segura
  • Quando usar stream().sorted() em vez de list.sort()

O objetivo não é memorizar, mas entender por que cada abordagem existe.

1.4 Para Quem Este Artigo É Destinado

Este artigo foi escrito para desenvolvedores que:

  • Entendem a sintaxe básica de Java (classes, métodos, Lists)
  • Já usaram ArrayList ou List antes
  • Ainda precisam procurar código de ordenação toda vez que precisam

Ele não se destina a iniciantes absolutos que nunca escreveram código Java,
mas é amigável para quem está aprendendo programação Java prática.

2. Três Formas Comuns de Ordenar uma List em Java (Qual Você Deve Usar?)

Em Java, não existe apenas um jeito de ordenar uma List.
Na prática, os desenvolvedores utilizam três abordagens diferentes, cada uma com comportamento e intenção ligeiramente diferentes.

Antes de aprofundar em Comparator, é importante entender quando e por que cada opção existe.

2.1 list.sort(Comparator) — A Abordagem Moderna e Recomendada

Desde o Java 8, esta é a forma padrão e mais recomendada de ordenar uma List.

list.sort(Comparator.naturalOrder());

Características principais

  • Definida diretamente na interface List
  • Clara e legível
  • Ordena a List original no local (destrutiva)

Ordenar objetos funciona da mesma maneira:

list.sort(Comparator.comparing(Person::getAge));

Quando usá‑la

  • Quando você está de acordo em modificar a List original
  • Quando deseja a solução mais clara e simples

👉 Se estiver em dúvida, list.sort() costuma ser a escolha certa.

2.2 Collections.sort(list) — Estilo Antigo que Você Ainda Precisa Reconhecer

Você pode encontrar código assim em tutoriais mais antigos ou projetos legados:

Collections.sort(list);

Ou com um Comparator:

%%CODEBLOCK7%%

(continua…)

Collections.sort(list, Comparator.reverseOrder());

Características principais

  • Existe desde o Java 1.2
  • Internamente se comporta quase como list.sort
  • Também modifica a List original

Por que é menos comum hoje

  • O Java 8 introduziu list.sort, que parece mais natural
  • Ordenar uma List via Collections é menos intuitivo

Para código novo, list.sort é preferido.
No entanto, entender Collections.sort ainda é importante ao ler bases de código mais antigas.

2.3 stream().sorted() — Ordenação Não Destrutiva

A terceira opção usa a API Stream.

List<Integer> sorted =
    list.stream()
        .sorted()
        .toList();

Com um Comparator:

List<Person> sorted =
    list.stream()
        .sorted(Comparator.comparing(Person::getAge))
        .toList();

Características principais

  • Não modifica a List original
  • Retorna uma nova List ordenada
  • Fácil de combinar com filter, map e outras operações de stream

Quando usá-lo

  • Quando você precisa manter a List original inalterada
  • Quando a ordenação faz parte de um pipeline de processamento de dados

Para ordenação simples, porém, list.sort costuma ser mais claro e eficiente.

2.4 Como Escolher (Guia de Decisão Rápida)

GoalRecommended Method
Sort a List directlylist.sort()
Understand or maintain old codeCollections.sort()
Keep the original List unchangedstream().sorted()

Neste ponto, lembre-se de apenas uma regra:

Use list.sort() a menos que você tenha uma razão clara para não fazê-lo.

3. Ordenação Básica: Ordem Ascendente e Descendente

Agora que você sabe qual método de ordenação usar, vamos nos concentrar no requisito mais comum:
ordenar em ordem ascendente ou descendente.

Esta seção cobre Lists de tipos básicos como números, strings e datas — a base para tudo que segue.

3.1 Ordenação em Ordem Ascendente (Ordem Natural)

Muitos tipos Java têm uma ordem natural, como:

  • Números → de menor para maior
  • Strings → ordem alfabética (A a Z)
  • Datas → mais antigas para mais recentes

Para ordenar uma List em ordem ascendente, use:

list.sort(Comparator.naturalOrder());

Exemplo: Ordenando números

List<Integer> numbers = Arrays.asList(5, 1, 3, 2);
numbers.sort(Comparator.naturalOrder());

Resultado:

[1, 2, 3, 5]

Exemplo: Ordenando strings

List<String> names = Arrays.asList("Tom", "Alice", "Bob");
names.sort(Comparator.naturalOrder());

Resultado:

[Alice, Bob, Tom]

👉 Se você só quer ordem ascendente, esta é a abordagem mais simples e segura.

3.2 Ordenação em Ordem Descendente

Para inverter a ordem natural, use Comparator.reverseOrder().

list.sort(Comparator.reverseOrder());

Exemplo: Números em ordem descendente

List<Integer> numbers = Arrays.asList(5, 1, 3, 2);
numbers.sort(Comparator.reverseOrder());

Resultado:

[5, 3, 2, 1]

Exemplo: Strings em ordem descendente

List<String> names = Arrays.asList("Tom", "Alice", "Bob");
names.sort(Comparator.reverseOrder());

Resultado:

[Tom, Bob, Alice]

3.3 Quando o Comparator Pode Ser Omitido

Em alguns casos, você pode ver código de ordenação escrito sem um Comparator explícito.

Collections.sort(list);

Ou até mesmo:

list.sort(null);

Isso funciona apenas se:

  • Os elementos implementam Comparable
  • Você deseja ordem natural (ascendente)

Embora válido, esses estilos são menos explícitos. Em bases de código reais, esta versão costuma ser preferida por clareza:

list.sort(Comparator.naturalOrder());

3.4 Um Equívoco Comum: Quem Decide a Ordem?

Uma fonte frequente de confusão para iniciantes é pensar que:

sort() decide a ordem ascendente ou descendente

Na prática:

  • sort() realiza a ordenação
  • Comparator define como os elementos são comparados

Depois que você entende essa separação de responsabilidades, todo o resto — ordenação de objetos, múltiplas condições e tratamento de null — se torna muito mais fácil.

4. Ordenando uma List de Objetos: Entendendo Comparator

Em aplicações Java do mundo real, você raramente ordena valores simples como Integer ou String.
Na maioria das vezes, você estará ordenando Listas de objetos personalizados.

É aqui que o Comparator se torna o conceito central.

4.1 O que é um Comparator?

Um Comparator define como dois elementos devem ser comparados.

Conceitualmente, ele responde a esta pergunta:

“Dado dois objetos, qual deve vir primeiro?”

Internamente, um Comparator retorna:

  • Um número negativo → o primeiro elemento vem primeiro
  • Zero → a ordem não importa
  • Um número positivo → o segundo elemento vem primeiro

Felizmente, no Java moderno você quase nunca precisa implementar essa lógica manualmente.

4.2 Ordenando por um Campo com Comparator.comparing

Considere a classe a seguir:

class Person {
    private String name;
    private int age;

    // getters omitted
}

Para ordenar um List<Person> por idade, use:

list.sort(Comparator.comparing(Person::getAge));

Isso lê naturalmente:

  • Pegue cada Person
  • Extraia a age
  • Compare esses valores
  • Ordene a Lista em ordem ascendente

Esta única linha substitui muitas linhas de código de comparação antigo.

4.3 Ordenando por Strings, Datas e Outros Tipos

O mesmo padrão funciona para quase qualquer tipo de campo.

Ordenar por nome

list.sort(Comparator.comparing(Person::getName));

Ordenar por data

list.sort(Comparator.comparing(Person::getBirthDate));

Desde que o valor extraído tenha uma ordem natural,
Comparator.comparing funcionará sem configuração adicional.

4.4 Usando Comparators Específicos para Primitivos

Para campos numéricos, o Java fornece métodos otimizados:

  • comparingInt
  • comparingLong
  • comparingDouble

Exemplo:

list.sort(Comparator.comparingInt(Person::getAge));

Esses métodos:

  • Evitam o empacotamento desnecessário de objetos
  • Tornam sua intenção mais clara
  • São ligeiramente mais eficientes para Listas grandes

Embora a diferença seja pequena, eles são considerados a melhor prática para campos numéricos.

4.5 Por que isso importa

Uma vez que você entende Comparator.comparing, você desbloqueia:

  • Ordenação com múltiplas condições
  • Ordem mista ascendente e descendente
  • Manipulação segura de valores null

Em outras palavras, esta é a base da ordenação prática de Listas em Java.

5. Ordenando com Múltiplas Condições (O Padrão Mais Comum no Mundo Real)

Em aplicações reais, ordenar por um único campo muitas vezes não é suficiente.
Você geralmente precisa de condições secundárias e terciárias para criar uma ordem estável e significativa.

Exemplos incluem:

  • Ordenar por idade, depois por nome
  • Ordenar por prioridade, depois por timestamp
  • Ordenar por pontuação (descendente), depois por ID (ascendente)

A API Comparator do Java foi projetada especificamente para isso.

5.1 O Básico de thenComparing

A ordenação com múltiplas condições segue uma regra simples:

Se dois elementos são iguais pela primeira condição, use a próxima condição.

Aqui está o padrão básico:

list.sort(
    Comparator.comparingInt(Person::getAge)
              .thenComparing(Person::getName)
);

Isso significa:

  1. Ordenar por age (ascendente)
  2. Se as idades forem iguais, ordenar por name (ascendente)

Isso produz uma ordem consistente e previsível.

5.2 Misturando Ordem Ascendente e Descendente

Muito frequentemente, você quer um campo em ordem descendente e outro em ordem ascendente.

Exemplo: Pontuação (descendente), Nome (ascendente)

list.sort(
    Comparator.comparingInt(Person::getScore).reversed()
              .thenComparing(Person::getName)
);

Detalhe importante:

  • reversed() se aplica apenas ao Comparator imediatamente antes dele

Isso torna seguro combinar direções de ordenação diferentes.

5.3 Passando um Comparator para thenComparing

Para melhor legibilidade, você pode definir explicitamente a direção de ordenação dentro de thenComparing.

Exemplo: Idade (ascendente), Data de Registro (descendente)

list.sort(
    Comparator.comparingInt(Person::getAge)
              .thenComparing(
                  Comparator.comparing(Person::getRegisterDate).reversed()
              )
);

Este estilo deixa muito claro quais campos são ascendentes ou descendentes,
o que é útil para revisões de código e manutenção a longo prazo.

5.4 Um Exemplo de Negócio Realista

list.sort(
    Comparator.comparingInt(Order::getPriority)
              .thenComparing(Order::getDeadline)
              .thenComparing(Order::getOrderId)
);

Lógica de ordenação:

  1. Prioridade mais alta primeiro
  2. Prazo mais próximo primeiro
  3. ID de pedido mais baixo primeiro

Isso garante uma ordem estável e amigável ao negócio.

5.5 Mantenha a Ordenação com Múltiplas Condições Legível

À medida que a lógica de ordenação cresce, a legibilidade se torna mais importante que a brevidade.

Melhores práticas:

  • Quebre linhas para cada condição
  • Evite lambdas profundamente aninhados
  • Adicione comentários se as regras de negócio não forem óbvias

Uma lógica de ordenação clara economiza tempo para todos que leem o código posteriormente.

6. Tratando Valores null com Segurança (Uma Fonte Muito Comum de Erros)

Ao ordenar dados do mundo real, valores null são quase inevitáveis.
Campos podem ser opcionais, dados legados podem estar incompletos ou valores podem simplesmente estar ausentes.

Se você não tratar null explicitamente, a ordenação pode falhar facilmente em tempo de execução.

6.1 Por que null Causa Problemas ao Ordenar

Considere este código:

list.sort(Comparator.comparing(Person::getName));

Se getName() retornar null para qualquer elemento, o Java lançará um
NullPointerException durante a comparação.

Isso acontece porque:

  • Um Comparator assume que os valores podem ser comparados
  • null não tem ordem natural a menos que você defina uma

Portanto, o tratamento de null deve ser explícito.

6.2 Usando nullsFirst e nullsLast

O Java fornece métodos auxiliares para definir como os valores null devem ser ordenados.

Colocar valores null primeiro

list.sort(
    Comparator.comparing(
        Person::getName,
        Comparator.nullsFirst(Comparator.naturalOrder())
    )
);

Colocar valores null por último

list.sort(
    Comparator.comparing(
        Person::getName,
        Comparator.nullsLast(Comparator.naturalOrder())
    )
);

Essas abordagens:

  • Prevêm NullPointerException
  • Tornam a regra de ordenação explícita e legível

6.3 Quando a Própria Lista Contém Elementos null

Às vezes, os elementos da Lista podem ser null.

List<Person> list = Arrays.asList(
    new Person("Alice", 20),
    null,
    new Person("Bob", 25)
);

Para tratar isso com segurança:

list.sort(
    Comparator.nullsLast(
        Comparator.comparing(Person::getName)
    )
);

Isso garante:

  • elementos null são movidos para o final
  • elementos não nulos são ordenados normalmente

6.4 Tenha Cuidado com comparingInt e null

Comparadores específicos de primitivos como comparingInt não podem lidar com null.

Comparator.comparingInt(Person::getAge); // age must be int

Se o campo for um Integer que pode ser null, use:

Comparator.comparing(
    Person::getAge,
    Comparator.nullsLast(Integer::compare)
);

Isso evita erros inesperados em tempo de execução.

6.5 Trate o Tratamento de null como Parte da Especificação

Decidir se os valores null devem aparecer:

  • No início
  • No final
  • Ou serem filtrados completamente

é uma decisão de negócio, não apenas técnica.

Usando nullsFirst ou nullsLast, você documenta essa decisão diretamente no código—
tornando sua lógica de ordenação mais segura e fácil de entender.

7. Armadilhas e Erros Comuns (Como Evitar Bugs Sutis)

Ordenar uma List em Java parece simples, mas há várias armadilhas fáceis de passar despercebidas que podem levar a bugs, comportamentos inesperados ou problemas de desempenho.

Entender essas armadilhas antecipadamente economizará tempo durante depuração e revisões de código.

7.1 Esquecer que a Ordenação é Destrutiva

Both list.sort() and Collections.sort() modificam a Lista original.

List<Integer> original = new ArrayList<>(List.of(3, 1, 2));
List<Integer> alias = original;

original.sort(Comparator.naturalOrder());

Neste caso:

  • original está ordenado
  • alias também está ordenado (porque referenciam a mesma Lista)

Como evitar isso

Se você precisar preservar a ordem original:

List<Integer> sorted = new ArrayList<>(original);
sorted.sort(Comparator.naturalOrder());

Ou use streams:

List<Integer> sorted =
    original.stream()
            .sorted()
            .toList();

Sempre pergunte a si mesmo:
“É aceitável modificar a Lista original?”

7.2 Consistência do Comparator e Ordenação Estável

Um Comparator deve produzir resultados consistentes e previsíveis.

Exemplo:

Comparator.comparing(Person::getAge);

Se várias pessoas têm a mesma idade, sua ordem relativa é indefinida.

Isso pode ser aceitável—mas frequentemente não é.

Boa prática

Adicione uma condição secundária para estabilizar a ordem:

Comparator.comparingInt(Person::getAge)
          .thenComparing(Person::getId);

Isso garante que o resultado da ordenação seja determinístico.

7.3 Sensibilidade a Maiúsculas e Minúsculas na Ordenação de Strings

A ordem natural de String é sensível a maiúsculas/minúsculas.

List<String> list = List.of("apple", "Banana", "orange");
list.sort(Comparator.naturalOrder());

Isso pode produzir resultados que parecem não intuitivos.

Ordenação sem distinção entre maiúsculas e minúsculas

list.sort(String.CASE_INSENSITIVE_ORDER);

Antes de escolher, considere:

  • É isso para exibição?
  • Ou para lógica interna?

A resposta determina a abordagem correta.

7.4 Realizando Trabalho Pesado Dentro de um Comparator

Um Comparator pode ser chamado muitas vezes durante a ordenação.

Evite:

  • Acesso a banco de dados
  • Chamadas de rede
  • Cálculos custosos
    // Bad idea (conceptual example)
    Comparator.comparing(p -> expensiveOperation(p));
    

Abordagem melhor

  • Pré‑calcular valores
  • Armazená‑los em campos
  • Comparar valores simples e baratos

Comparators eficientes fazem uma grande diferença com Listas grandes.

7.5 Priorize Legibilidade Sobre Astúcia

A lógica de ordenação costuma ser lida mais vezes do que escrita.

Em vez de:

  • Uma única expressão encadeada longa
  • Lambdas profundamente aninhadas

Prefira:

  • Quebras de linha
  • Estrutura clara
  • Comentários opcionais para regras de negócio

Código de ordenação legível reduz bugs e facilita a manutenção.

8. Considerações de Performance e Escolha da Abordagem Correta

Até agora, você sabe como ordenar Listas em Java.
Esta seção foca em qual abordagem escolher sob a perspectiva de performance e design.

Na maioria das aplicações, a ordenação não é um gargalo—mas escolhas ruins ainda podem causar sobrecarga desnecessária.

8.1 list.sort() vs stream().sorted()

Este é o ponto de decisão mais comum.

list.sort()

list.sort(Comparator.comparingInt(Person::getAge));

Prós

  • Nenhuma alocação extra de Lista
  • Intenção clara: “ordenar esta Lista”
  • Um pouco mais eficiente

Contras

  • Modifica a Lista original

stream().sorted()

List<Person> sorted =
    list.stream()
        .sorted(Comparator.comparingInt(Person::getAge))
        .toList();

Prós

  • Lista original permanece inalterada
  • Encaixa naturalmente em pipelines de streams

Contras

  • Aloca uma nova Lista
  • Um pouco mais de sobrecarga

Regra prática

  • Ordenação simpleslist.sort()
  • Pipelines de transformação ou imutabilidadestream().sorted()

8.2 Ordenando Listas Grandes de Forma Eficiente

Algoritmos de ordenação chamam o Comparator muitas vezes.
Para Listas grandes, isso importa.

Diretrizes principais

  • Mantenha os comparators leves
  • Evite cadeias de métodos que realizem trabalho pesado
  • Prefira comparators primitivos ( comparingInt, etc.)

Exemplo: Pré‑calcular chaves caras

Em vez de:

Comparator.comparing(p -> calculateScore(p));

Faça:

// Precompute once
p.setScore(calculateScore(p));

Então ordene pelo campo:

Comparator.comparingInt(Person::getScore);

Isso reduz drasticamente o trabalho repetido durante a ordenação.

8.3 O Collections.sort() Alguma Vez é a Escolha Certa?

Para código novo, quase nunca.

No entanto, ainda aparece em:

  • Projetos legados
  • Tutoriais mais antigos
  • Bases de código Java 7 e anteriores

Você não precisa usá-lo—mas deve reconhecê-lo.

8.4 Lista de Verificação de Decisão Recomendada

Antes de ordenar, pergunte:

  1. Posso modificar a List original?
  2. Preciso de múltiplas condições de ordenação?
  3. Algum campo pode ser null ?
  4. O desempenho é importante em escala?

Responder a essas perguntas naturalmente leva à solução correta.

9. Resumo: Guia Rápido de Ordenação de Listas Java

Vamos resumir tudo em um guia de referência rápida em que você pode confiar.

9.1 Padrões Rápidos que Você Usará com Mais Frequência

Ordem crescente

list.sort(Comparator.naturalOrder());

Ordem decrescente

list.sort(Comparator.reverseOrder());

9.2 Ordenando Objetos por um Campo

list.sort(Comparator.comparing(Person::getName));

Para números:

list.sort(Comparator.comparingInt(Person::getAge));

9.3 Múltiplas Condições

list.sort(
    Comparator.comparingInt(Person::getAge)
              .thenComparing(Person::getName)
);

Ordem mista:

list.sort(
    Comparator.comparingInt(Person::getScore).reversed()
              .thenComparing(Person::getName)
);

9.4 Tratamento Seguro de null

list.sort(
    Comparator.comparing(
        Person::getName,
        Comparator.nullsLast(Comparator.naturalOrder())
    )
);

A List pode conter elementos null:

list.sort(
    Comparator.nullsLast(
        Comparator.comparing(Person::getName)
    )
);

9.5 Ordenação Não Destrutiva

List<Person> sorted =
    list.stream()
        .sorted(Comparator.comparingInt(Person::getAge))
        .toList();

9.6 Conclusão Final

A ordenação de Listas Java se torna simples quando você lembra:

  • Comparator define a ordem
  • sort() executa a operação
  • Clareza supera esperteza
  • Tratamento explícito de null evita bugs

Se você internalizar esses princípios,
nunca mais precisará “reaprender” a ordenação de Listas Java.