Ordenando coleções com Comparable e Comparator
Postado em 22. jun, 2010 por Nico Steppat 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
17 Respostas para “Ordenando coleções com Comparable e Comparator”
Trackbacks/Pingbacks
-
-
fevereiro 10, 2011
[...] Ordenando coleções com Comparable e Comparatorpor Nico Steppat, em 22/06 [...]
ASSINE NOSSO RSS




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.
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.
Jeferson
25. jun, 2010
otimo topico, ja me esclareceu muito, tanto quanto o de StringBuffer vs StringBuilder
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()
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…
Rodrigo Facholi
28. jun, 2010
Muito bom artigo, e ótima dica no comentário do william!
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
Paulo Silveira
02. jul, 2010
Oi Fernando. Poste seu codigo pra gente ver, mas nao falto nada nao!
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.
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.
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
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
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?
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);
}
}
Nico Steppat
01. ago, 2011
Oi javaneiro,
vc deve iterar com o mapa “ordenada”, o mapa “medias” continua sem ordem.
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