Ordenando coleções com Comparable e Comparator

Postado em 22. jun, 2010 por em Java

Uma tarefa comum no dia-a-dia dos desenvolvedores é ordenar uma lista ou array. Para não inventar a roda, a Collections API do Java (também conhecida pelo nome do seu pacote, o java.util) vem pronta para ajudar nessa tarefa. Falamos dessa API extensivamente na apostila do curso FJ-11, e vou aqui passar para o problema específico de ordenação.

Imagine que você gostaria de ordenar uma lista de Contas bancárias. Cada conta possui um número (int) e um titular (String):

Conta conta1 = new Conta(5452, "Phillip Lahm");
Conta conta2 = new Conta(1234, "Lucas Podolski");
Conta conta3 = new Conta(3145, "Arne Friedrich");

List<Conta> lista = new ArrayList<Conta>();
lista.add(conta1);
lista.add(conta2);
lista.add(conta3);

O método para ordenar uma lista se encontra na classe java.util.Collections (repare o “s” no final). Ela possui métodos estáticos que ajudam a manipular coleções, entre eles o método sort. Assim podemos tentar ordenar a lista de contas:

Collections.sort(lista);

Mas infelizmente a linha acima nem compila. Antes de invocar o método sort é preciso definir o critério de ordenação: uma forma de informar, dado duas contas, qual vem “antes” e qual vem “depois. Considerando que queremos ordenar pelo número da conta, a classe Conta implementar a interface java.lang.Comparable que define o que será nossa “ordem natural”. A interface possui apenas um método compareTo:

public interface Comparable<T> {
    int compareTo(T outro);
}

As contas então devem ser comparáveis. Vamos definir a ordem natural baseada no número da conta:

public Conta implements Comparable<Conta> {

    private int numero;
    private String titular;
    // outros metodos e atributos

    public int compareTo(Conta outraConta) {
        if (this.numero < outraConta.numero) {
            return -1;
        }
        if (this.numero > outraConta.numero) {
            return 1;
        }
        return 0;
    }
}

Se o número da conta atual é menor do que da outraConta retormamos -1 (ou qualquer int negativo, indicando que this deve vir “antes” de outraConta), se for maior retornamos 1 (ou qualquer int positivo) e se for igual então devolvemos 0.

Agora podemos invocar Collections.sort(lista).

Mas se surgir a necessidade de ordenar pelo titular da conta? Não queremos alterar o método compareTo na classe Conta, já que isso mudaria a ordem natural. Queremos definir um outro critério de ordenação. Para tal, existe uma outra interface: a Comparator:

public interface Comparator<T> {
    int compare(T o1, T o2);
}

Vamos então implementar a interface para definir a ordem pelo titular (String) da conta. Comparar duas Strings é difícil, mas, como você pode imaginar, esse problema já foi resolvido na API do Java. A classe String já sabe compara dois strings, sabemos isso pois ela implementa a interface Comparable (por esse mesmo motivo podemos invocar Collections.sort para uma List de String). Podemos então delegar essa tarefa ao método compareTo das Strings:

public class TitularComparator implements Comparator<Conta> {
    public int compare(Conta conta, Conta outraConta) {
        return conta.getTitular().
                compareTo(outraConta.getTitular());
    }
}

Como escolher para que esse critério de comparação seja utilizado em vez da ordem natural? O método sort é sobrecarregado e pode receber um objeto do tipo Comparator:

TitularComparator comparator = new TitularComparator();
Collections.sort(lista, comparator);

Falta mencionar que o método compareTo da interface Comparable deve ser consistente com o método equals. Quando uma conta é igual a outra (a classe Conta sobreescreve o método equals para definir igualdade), o método compareTo deve devolver zero também. Devemos também pensar se receberemos null como Contas para ordenar, e tomar as devidas precauções nos comparadores.

Através de Comparable e Comparator também são controladas a ordenação da coleção TreeSet e as chaves do mapa TreeMap.

Esse tópico também faz parte da prova de certificação de programador, a SCJP.

Nico Steppat

Mais sobre o autor

Tags: , , , ,

17 Respostas para “Ordenando coleções com Comparable e Comparator”

  1. Faru

    24. jun, 2010

    Só uma pequena correção,
    Em um trecho você diz que a interface Comparable é do pacote java.util, e na verdade ela é do java.lang

    Sei que é algo simples, mas pode confundir.

  2. Paulo Silveira

    24. jun, 2010

    Oi Faru, perfeito, ja fiz a alteracao. O Nico colocou o link pra java lang mas havia digitado o nome errado.

  3. Jeferson

    25. jun, 2010

    otimo topico, ja me esclareceu muito, tanto quanto o de StringBuffer vs StringBuilder

  4. william

    25. jun, 2010

    Grande artigo…

    gosto de usar em conjunto com enums para deixar um pouco mais fluente, tipo…

    public enum OrdenarClientes implements Comparator {
    
    	PorIdade() {
            public int compare(Cliente one, Cliente other) {
               return one.getIdade.compareTo(other.getIdade());
            }
         },
    
         PorNome() {
             public int compare(Cliente one, Cliente other) {
                return one.getNome().compareTo(other.getNome());
             }
          };
    
         public abstract int compare(Cliente one, Cliente other);
    
         public Comparator asc() {
            return this;
         }
    
         public Comparator desc() {
            return Collections.reverseOrder(this);
         }
    }
    

    dai posso fazer coisas como OrdenarClientes.PorNome().asc()

  5. Paulo Silveira

    26. jun, 2010

    @William excelente codigo! é realmente bastante util, da para aumentar ainda mais sua ideia e criar uma fluent interface para ainda configurar casos de empate, etc…

  6. Rodrigo Facholi

    28. jun, 2010

    Muito bom artigo, e ótima dica no comentário do william!

  7. fernando

    02. jul, 2010

    primeiramente parabéns pela explicação, cara eu fiz exatamente como está no seu post, tenho um arrayList e quero ordená-lo sempre de acordo com um atributo dos objetos que estao no array, na classe de dominio implementei a comparable e fiz o compareTo com o atributo que será levado em conta na ordenação do mesmo jeito que vc fez no post(meu atributo também é um int) após isso chamei a collections.sort passando meu array de objetos e o array continuou com a mesma ordenação, faltou eu fazer alguma coisa? obrigado

  8. Paulo Silveira

    02. jul, 2010

    Oi Fernando. Poste seu codigo pra gente ver, mas nao falto nada nao!

  9. Oziel Alves Cavalcante

    07. jul, 2010

    Vendo no exemplo que TitularComparator é uma public class e lembrando da static nested class CaseInsensitiveComparator usada na classe String, me veio a dúvida se há uma solução melhor entre as duas ou se depende do caso. Dúvida também válida para o exemplo de enum do William, externo ou interno à uma classe?

    @William como disse o pimeja, http://tobega.blogspot.com/2008/05/beautiful-enums.html, o Comparator deve ser parametrizado e o método abstrato parece ser desnecessário.

    @Caelum, a sintaxe dos comments suporta html? Pelo que vi suporta formatação de código.

  10. Paulo Silveira

    07. jul, 2010

    @ Oziel. Voce pode fazer comentarios formatados entre [ java ] e [/java].

    Sobre usar uma classe aninhada estatica, é interessante para deixar claro que aquilo só é pra ser usado com a classe String, e para nao poluir o pacote com classes pequenas… mas é bastante questionável. Uma classe publica top level teria o mesmo sentido (e é minha preferencia na maioria dos casos).

    O metodo abstrato é desnecessário mesmo, e o tipo realmente deve ser parametrizado (Acho que no post do tobe nao apareceu porque o html comeu o sinal de maior e de menor) senao o metodo compare receberia dois Objects.

  11. Larissa

    26. dez, 2010

    Oi…estou com uma dúvida…

    eu tenho um List listaCaixas = new ArrayList ();

    como faço para ordenar essa listaCaixas pelo atributo volume da classe Caixa…e tem que ser da caixa com maior volume para a que tiver menor volume..o compareTo não está aceitando tipo double.

    obrigada

  12. thejokerbm

    25. abr, 2011

    na verdade a sua classe caixa tem q implementar comparable ai vc comprar dentro do metodo compareTo, mas procure no GUJ la vc vai encontrar mais informação

  13. javaneiro

    01. ago, 2011

    Ótimo artigo, entao tentei fazer uma ordenação de um HashMap medias pelo String…

    Mas não deu certo… fiz da seguinte forma usando uma coleção ordenada…

    TreeMap ordenado = new TreeMap(medias);
    Iterator i=medias.keySet().iterator();
    while (i.hasNext()){
    String chave=(String)i.next();
    int valor=(int)medias.get(chave);
    System.out.println(chave + “:” + valor);
    }
    O que posso fazer nesse caso?

  14. javaneiro

    01. ago, 2011

    Funcionou sim… obrigado… só passei a coleção errada iterator… ficando assim…

    private void ordenandoHashMap(HashMap medias) {

    TreeMap ordenado = new TreeMap(medias);
    Iterator i = ordenado.keySet().iterator();
    while (i.hasNext()){
    String chave=(String)i.next();
    int valor=(int)medias.get(chave);
    System.out.println(chave + “:” + valor);
    }

    }

  15. Nico Steppat

    01. ago, 2011

    Oi javaneiro,

    vc deve iterar com o mapa “ordenada”, o mapa “medias” continua sem ordem.

  16. João Henrique

    14. mar, 2012

    Lembrando que comparator possibilita apenas um tipo de ordenação.

    Gosto de usar inner Class para fazer este serviço.

    Crio uma inner class do Tipo OrdenacaoPorNome que implementa comparable

    E ai passo essa classe comparavel para o método sort de Collections

Trackbacks/Pingbacks

  1. TOP 10: Os melhores posts de 2010 da Caelum | blog.caelum.com.br - fevereiro 10, 2011

    [...] Ordenando coleções com Comparable e Comparatorpor Nico Steppat, em 22/06 [...]

Deixar uma Resposta