Estou partindo do pressuposto que você saiba trabalhar minimamente com generics, ok?
Bom, existem duas interfaces nessa brincadeira: A Comparable e a Comparator. Ambas têm o propósito de estabelecer uma relação de ordenação entre objetos. Por exemplo: Vamos supor que você cria um uma classe Quadrado
01 public class Quadrado {
02 private int aresta;
03
04 public Quadrado(int aresta) {
05 if(aresta <= 0)
06 throw new IllegalArgumentException("aresta = " + aresta);
07 this.aresta = aresta;
08 }
09
10 public int getAresta() {
11 return aresta;
12 }
13
14 public long getArea() {
15 return (long)Math.pow(aresta, 2);
16 }
17
18 public double getHipotenusa() {
19 return Math.sqrt(2 * Math.pow(aresta, 2));
20 }
21
22 public boolean equals(Object obj) {
23 if(!(obj instanceof Quadrado))
24 return false;
25
26 Quadrado outro = (Quadrado)obj;
27
28 return aresta == outro.aresta;
29 }
30
31 public String toString() {
32 return
33 "[aresta=" + aresta +
34 "; area=" + getArea() +
35 "; hipotenusa=" + getHipotenusa() + "]"
36 ;
37 }
38 }
Esta é uma classe bem simples, só pra representar um quadrado mesmo. Essa classe disponibiliza um mecanismo que nos permite verificar se dois objetos do tipo Quadrado são equivalentes ou não: O método equals.
Vamos supor agora que queremos agora estabelecer uma ordenação entre quadrados. Digamos que nosso critério seja o seguinte:
:arrow: Um quadrado q1 é maior que um quadrado q2, se a aresta de q1 for maior que a aresta de q2.
:arrow: Um quadrado q1 é menor que um quadrado q2, se a aresta de q1 for menor que a aresta de q2.
:arrow: Um quadrado q1 é igual a um quadrado q2, se a aresta de q1 for igual a aresta de q2.
Mas… essa classe por acaso disponibiliza algum mecanismo que nos permita descobrir se um determinado objeto Quadrado é maior ou menor que um outro objeto Quadrado? A resposta é não. Para podermos verificar se um quadrado é maior ou menor que o outro, segundo o critério mencionado acima, nós mesmos teriamos que fazer algum algoritmozinho pra determinar quando um Quadrado é menor ou maior que outro:
01 public class TesteQuadrado {
02 public static void main(String[] args) {
03 Quadrado[] quadrados = {
04 new Quadrado(1),
05 new Quadrado(35),
06 new Quadrado(12),
07 new Quadrado(35),
08 new Quadrado(12),
09 new Quadrado(1),
10 };
11
12 for(int quad = 0; quad < quadrados.length; quad++) {
13 System.out.println("O quadrado " + quadrados[quad] + " é ");
14 for (int cmp = 0; cmp < quadrados.length; cmp++) {
15 String comparacao;
16 if(quadrados[quad].getAresta() > quadrados[cmp].getAresta())
17 comparacao = " maior que ";
18 else
19 if(quadrados[quad].getAresta() < quadrados[cmp].getAresta())
20 comparacao = " menor que ";
21 else
22 comparacao = " igual a";
23
24 System.out.println(
25 "\t" + comparacao + "o quadrado " + quadrados[cmp]
26 );
27 }
28 }
29 }
30 }
Este programa pega um punhado de objetos do tipo Quadrado e compara-os entre si, exibindo na tela a conclusão das comparações. A saída fica assim:
O quadrado [aresta=1; area=1; hipotenusa=1.4142135623730951] é
igual ao quadrado [aresta=1; area=1; hipotenusa=1.4142135623730951]
menor que o quadrado [aresta=35; area=1225; hipotenusa=49.49747468305833]
menor que o quadrado [aresta=12; area=144; hipotenusa=16.97056274847714]
menor que o quadrado [aresta=35; area=1225; hipotenusa=49.49747468305833]
menor que o quadrado [aresta=12; area=144; hipotenusa=16.97056274847714]
igual ao quadrado [aresta=1; area=1; hipotenusa=1.4142135623730951]
O quadrado [aresta=35; area=1225; hipotenusa=49.49747468305833] é
maior que o quadrado [aresta=1; area=1; hipotenusa=1.4142135623730951]
igual ao quadrado [aresta=35; area=1225; hipotenusa=49.49747468305833]
maior que o quadrado [aresta=12; area=144; hipotenusa=16.97056274847714]
igual ao quadrado [aresta=35; area=1225; hipotenusa=49.49747468305833]
maior que o quadrado [aresta=12; area=144; hipotenusa=16.97056274847714]
maior que o quadrado [aresta=1; area=1; hipotenusa=1.4142135623730951]
O quadrado [aresta=12; area=144; hipotenusa=16.97056274847714] é
maior que o quadrado [aresta=1; area=1; hipotenusa=1.4142135623730951]
menor que o quadrado [aresta=35; area=1225; hipotenusa=49.49747468305833]
igual ao quadrado [aresta=12; area=144; hipotenusa=16.97056274847714]
menor que o quadrado [aresta=35; area=1225; hipotenusa=49.49747468305833]
igual ao quadrado [aresta=12; area=144; hipotenusa=16.97056274847714]
maior que o quadrado [aresta=1; area=1; hipotenusa=1.4142135623730951]
O quadrado [aresta=35; area=1225; hipotenusa=49.49747468305833] é
maior que o quadrado [aresta=1; area=1; hipotenusa=1.4142135623730951]
igual ao quadrado [aresta=35; area=1225; hipotenusa=49.49747468305833]
maior que o quadrado [aresta=12; area=144; hipotenusa=16.97056274847714]
igual ao quadrado [aresta=35; area=1225; hipotenusa=49.49747468305833]
maior que o quadrado [aresta=12; area=144; hipotenusa=16.97056274847714]
maior que o quadrado [aresta=1; area=1; hipotenusa=1.4142135623730951]
O quadrado [aresta=12; area=144; hipotenusa=16.97056274847714] é
maior que o quadrado [aresta=1; area=1; hipotenusa=1.4142135623730951]
menor que o quadrado [aresta=35; area=1225; hipotenusa=49.49747468305833]
igual ao quadrado [aresta=12; area=144; hipotenusa=16.97056274847714]
menor que o quadrado [aresta=35; area=1225; hipotenusa=49.49747468305833]
igual ao quadrado [aresta=12; area=144; hipotenusa=16.97056274847714]
maior que o quadrado [aresta=1; area=1; hipotenusa=1.4142135623730951]
O quadrado [aresta=1; area=1; hipotenusa=1.4142135623730951] é
igual ao quadrado [aresta=1; area=1; hipotenusa=1.4142135623730951]
menor que o quadrado [aresta=35; area=1225; hipotenusa=49.49747468305833]
menor que o quadrado [aresta=12; area=144; hipotenusa=16.97056274847714]
menor que o quadrado [aresta=35; area=1225; hipotenusa=49.49747468305833]
menor que o quadrado [aresta=12; area=144; hipotenusa=16.97056274847714]
igual ao quadrado [aresta=1; area=1; hipotenusa=1.4142135623730951]
Perceba que neste caso, a comparação entre dois objetos Quadrado é feita fora da classe Quadrado. Esta comparação que fizemos não é uma comparação natural de um objeto Quadrado, ou seja, um objeto do tipo Quadrado não sabe olhar para outro Quadrado e dizer se ele próprio é maior, menor ou igual ao Quadrado para o qual ele está olhando…
Isso é meio chato, por que não teremos nunca como garantir um critério padrão para comparar objetos Quadrado. Digo isso pelo seguinte: Os critérios que utilizamos acima, fomos nós que definimos. Nada impede de um Joselito maluco da vida estabelecer um outro algoritmo, com um critério totalmente diferente do nosso para estipular quem é maior, menor ou igual a quem. O legal mesmo seria a própria classe Quadrado estabelecer o critério de comparação entre seus objetos. Nada mais justo, não é mesmo?
E de que forma podemos fazer isto? Bom, podemos seguir nossa intuiçao e colocar um método lá dentro da classe Quadrado com esse propósito. Que tal um método chamado, por exemplo, compararCom? O nome é bem intuitivo, né mesmo? Ok, mas agora vamos pensar em como esse método vai funcionar, para que possamos definir sua assinatura. A idéia doe colocarmos o método compararCom na classe Quadrado é permitir que um objeto desta classeQuadrado(q1, por exemplo) possa se auto-comparar com outro objeto do tipo [i]Quadrado/i. Isso nos leva a imaginar que queremos poder fazer algo assim com o q1:
q1.compararCom(q2)
Beleza! Creio que já temos então a assinatura do nosso método:
compararCom(Quadrado)
Mas… e que diabos esse método vai retornar??? Bom, o retorno desse método tem que ser alguma informação que nos diga de forma clara se q1 é maior, menor ou igual a q2. Que tal se o método sempre retornar um int? O valor desse int retornado seguiria a seguinte regrinha:
:arrow: Se o q1(O objeto que invoca o compararCom) for maior que q2(O objeto passado por parâmetro para o compararCom), o int retornado será um inteiro extritamente negativo(<0) qualquer.
:arrow: Se o q1 for maior que q2, o int retornado será um inteiro estritamente positivo(>0) qualquer.
:arrow: Se o q1 for igual à q2, o int retornado será o inteiro zero.
Legal! O nosso método já tem um cabeçalho completo!
public int compararCom(Quadrado)
Agora, vamos tentar adicionar esse método à nossa classe Quadrado:
01 public class Quadrado {
02 private int aresta;
03
04 public Quadrado(int aresta) {
05 if(aresta <= 0)
06 throw new IllegalArgumentException("aresta = " + aresta);
07 this.aresta = aresta;
08 }
09
10 public int getAresta() {
11 return aresta;
12 }
13
14 public long getArea() {
15 return (long)Math.pow(aresta, 2);
16 }
17
18 public double getHipotenusa() {
19 return Math.sqrt(2 * Math.pow(aresta, 2));
20 }
21
22 public boolean equals(Object obj) {
23 if(!(obj instanceof Quadrado))
24 return false;
25
26 Quadrado outro = (Quadrado)obj;
27
28 return compararCom(outro) == 0;
29 }
30
31 public int compararCom(Quadrado outro) {
32 return this.aresta - outro.aresta;
33 }
34
35 public String toString() {
36 return
37 "[aresta=" + aresta +
38 "; area=" + getArea() +
39 "; hipotenusa=" + getHipotenusa() + "]"
40 ;
41 }
42 }
Na linha 31 aparece nosso novo método!!! Veja como ficou simples a implementação dele: Se a aresta do objeto que invocou o compararCom(representado pelo this) for maior que a aresta do objeto passado por parâmetro(representado por outro), então isso quer dizer que o objeto que invocou é maior que o objeto passado por parâmetro. Neste caso, o método deveria retornar um inteiro positivo, e é exatamente isso que ele faz! Como a aresta do this é maior que a aresta do outro, a primeira menos a segunda dá um valor positivo. E assim por diante.
Note também que alteramos o método equals, mais precisamente a linha 33. Estamos atrelando o resultado do equals com o resultado do compararCom. Fazemos isso porque parece ser extremamente consistente o equals retornar true quando o compararCom retornar 0, e vice-versa, uma vez que esses dois casos trazem a mesma semântica (significado).
Agora, em qualquer lugar do mundo em que nossa classe Quadrado for utilizada, os programadores poderão utilizar o método compararCom para conseguir obter uma ordenação natural de objetos do tipo Quadrado. Basta eles utilizarem assim nossa classe:
01 public class TesteQuadrado {
02 public static void main(String[] args) {
03 Quadrado[] quadrados = {
04 new Quadrado(1),
05 new Quadrado(35),
06 new Quadrado(12),
07 new Quadrado(35),
08 new Quadrado(12),
09 new Quadrado(1),
10 };
11
12 for(int quad = 0; quad < quadrados.length; quad++) {
13 System.out.println("O quadrado " + quadrados[quad] + " é ");
14 for (int cmp = 0; cmp < quadrados.length; cmp++) {
15 String comparacao;
16 if(quadrados[quad].compararCom(quadrados[cmp]) > 0)
17 comparacao = " maior que ";
18 else
19 if(quadrados[quad].compararCom(quadrados[cmp]) < 0)
20 comparacao = " menor que ";
21 else
22 comparacao = " igual a";
23
24 System.out.println(
25 "\t" + comparacao + "o quadrado " + quadrados[cmp]
26 );
27 }
28 }
29 }
30 }
Basicamente, o que mudou foram as linhas 16 e 19. Ao invez de compararmos os resultados dos getAresta de cada objeto, utilizamos o resultado do compararCom para determinarmos se quadrados[quad] é maior, menor ou igual a quadrados[cmp]. Pouca coisa de vantagem? parece que sim, mas não é bem por aí… Parece que a vantagem é pequena porque o critério de comparação utilizado por Quadrado é relativamente simples. Imagine se tivessemos uma classe cujo critério de comparação dependesse de… hmm… 5 de seus 10 campos… :twisted: :shock: :shock: :shock: :twisted:
Outro ponto a favor, é que o critério em si, bem como sua implementação, ficam encapsulados dentro da classe, transparentes a quem vai utilizar a classe Quadrado.
Ok! Nossa classe agora tem um mecanismo próprio para estabelecer uma ordenação natural entre seus objetos. Legal! Mas se você acha que acabamos de inventar uma coisa nova, está redondamente enganado… Esse conceito de classes que definel uma ordenação natural para seus objetos vem deeeesde o Java 1.2. Tanto é, que existem classes da API Java que tiram proveito de objetos cujas classes determinam uma ordenação natural. Algumas classes e interfaces da API Java utilizam objetos desse tipo para criar conjuntos ordenados. Há também classes que têm métodos próprios para ordenar arrays e listas de objetos com base na ordenação natural destes.
Mas cabe aqui uma pergunta: Se toda essa infra-estrutura existe desde o Java 1.2 (bem velhinho…), como é que essas classes do Java mencionadas acima, que são mais velhas que a nossa classe Quadrado, vão advinhar que na nossa classe o método que faz a comparação chama-se compararCom, recebe um parâmetro do tipo Quadrado e retorna um int??? Simples: Elas não vão advinhar nada…
Mas então, como podemos “conectar” nossa classe Quadrado com essas classes do Java?
Estas classes da API Java que utilizam objetos auto-comparáveis estabelecem uma regrinha para a classe dos objetos que elas manipulam: A classe deve implementar a interface [i]Comparable/i. Vamos pegar um exemplo: A classe [i]TreeSet/i implementa um conjunto de objetos que estão sempre ordenados. É como se fosse uma lista de objetos, que sempre está ordenada, e não tem objetos repetidos(objetos iguais). Sempre que você adiciona um objeto dentro de um TreeSet, esse TreeSet vai verificar se a classe do objeto que você inseriu determina uma ordenação natural. De que forma ele faz isso? Simples: verficando se a classe deste objeto dá um implements da interface Comparable! Veja o seguinte exemplo:
01 import java.util.TreeSet;
02
03 public class TesteTreeSet01 {
04 public static void main(String[] args) {
05 /*
06 * Criamos uma instância de TreeSet que vai armazenar objetos do tipo
07 * Quadrado
08 */
09 TreeSet<Quadrado> setQuadrados = new TreeSet<Quadrado>();
10 /*
11 * Criamos um vetor de tipo Quadrado com um punhado de objetos Quadrado
12 * fora de ordem
13 */
14 Quadrado[] quadrados = {
15 new Quadrado(1),
16 new Quadrado(35),
17 new Quadrado(12),
18 new Quadrado(35),
19 new Quadrado(12),
20 new Quadrado(1),
21 };
22
23 System.out.println("Quadrados desordenados:");
24 for(Quadrado quadrado: quadrados) {
25 System.out.println("\t" + quadrado);
26 }
27 System.out.println();
28
29 /*
30 * Adicionamos ao TreeSet setQuadrados, um a um, os objetos Quadrado
31 * contidos no vetor quadrados. O método add da classe TreeSet, insere
32 * o objeto passado já na ordem certa.
33 */
34 for (int q = 0; q < quadrados.length; q++) {
35 setQuadrados.add(quadrados[q]);
36 }
37
38 System.out.println("Quadrados ordenados:");
39 for(Quadrado quadrado: setQuadrados) {
40 System.out.println("\t" + quadrado);
41 }
42 }
43 }
Neste código, estamos criando uma instância de TreeSet, que armazenará ordenadamente os elementos do vetor de Quadrado quadrados. Intuitivamente, bastaria adicionar os objetos Quadrado em setQuadrados e pronto! Teríamos um conjunto de quadrados ordenado e sem repetições. Porém, há um pequeno problema: A classe TreeSet não tem como advinhar que a classe Quadrado estabelece uma ordenação natural… A classe Quadrado teria que “dar uma dica” disso pra classe TreeSet… Se executarmos o código acima, teremos uma saída mais ou menos assim:
Quadrados desordenados:
[aresta=1; area=1; hipotenusa=1.4142135623730951]
[aresta=35; area=1225; hipotenusa=49.49747468305833]
[aresta=12; area=144; hipotenusa=16.97056274847714]
[aresta=35; area=1225; hipotenusa=49.49747468305833]
[aresta=12; area=144; hipotenusa=16.97056274847714]
[aresta=1; area=1; hipotenusa=1.4142135623730951]
Exception in thread "main" java.lang.ClassCastException: Quadrado
at java.util.TreeMap.compare(TreeMap.java:1093)
at java.util.TreeMap.put(TreeMap.java:465)
at java.util.TreeSet.add(TreeSet.java:210)
at TesteTreeSet01.main(TesteTreeSet01.java:40)
Uma exceção lançada pela classe TreeSet!!! Mas por quê? Porque como a classe TreeSet esperava que a classe do objeto inserido (Quadrado) implementasse a interface Comparable! A classe TreeSet confiava que a classe Quadrado implementasse a interface Comparable. Mas nós traímos tal confiança… Em resposta, somos punidos com o lançamento de uma ClassCastException. Isso acontece porque, internamente, ao ser inserido um elemento (que não o primeiro do TreeSet), o TreeSet vai querer comparar o elemento sendo inserido com os elementos já existentes na lista. Pra isso, ele vai tentar fazer um cast mais ou menos assim:
Comparable objComparavelSendoInserido =
(Comparable)quadradoSendoInserido;
Notou como a classe TreeSet “confia” que o objeto inserido é de uma classe que implementa Comparable? Ela vai lá e faz o cast direto. Bom, como a nossa classe Quadrado não implementa Comparable, a instrução acima conhecidamente lança uma ClassCastException.
Bom, agora sabemos que as classes da API java que utilizam algum mecanismo de comparação de outros objetos, geralmente exigem que esses objetos sejam objetos Comparable.
Mas aí cabe uma pergunta: De que adianta a nossa classe implementar Comparable? Por acaso isso vai fazer alguma mágica capaz de dar poderes de advinhação ao, por exemplo, TreeSet, permitindo que ele “descubra” qual é o método que faz a comparação?
Resposta: Quase isso… Na verdade, a interface Comparable declara um método com a seguinte assinatura:
public int compareTo(T)
Familiar essa assinatura, não? :twisted:
O contrato (A grosso modo: regras de uso. Procurem ler a respeito de “design by contract”) do método compareTo, em linhas gerais, estabelece que a implementação deste método deve estabelecer uma ordenação aos objetos da classe que dá implements em Comparable. Partindo do pressuposto que uma classe que implementa uma interface, obrigatoriamente implementa seus métodos, e partindo também do pressuposto que o contrato dos métodos implementados foi “honrado”, um TreeSet faz algo parecido com isso:
for(T elementoJaInserido: todosElementosInseridos){
Comparable comparavelSendoInserido = (Comparable) elementoSendoInserido;
int resultadoComparacao = comparavelSendoInserido.compareTo(elementoJaInserido);
//continua...
}
É dessa forma que o TreeSet faz a comparação: Como ele assume que os elementos são objetos de classes que dão implements em Comparable, nada mais justo de admitir, por conseqüência, que estes objetos podem ser convertido para Comparable e utilizar então o método compareTo desta interface.
Essa é a “dica” que a classe Quadrado deve dar para que, não só as classes da API Java mencionadas antes, mas também para todos nós programadores, de que seus objetos têm uma ordenação natural: implements Comparable!!!
Façamos a seguinte alteração na classe Quadrado:
01 public class Quadrado implements Comparable<Quadrado>{
02 private int aresta;
03
04 public Quadrado(int aresta) {
05 if(aresta <= 0)
06 throw new IllegalArgumentException("aresta = " + aresta);
07 this.aresta = aresta;
08 }
09
10 public int getAresta() {
11 return aresta;
12 }
13
14 public long getArea() {
15 return (long)Math.pow(aresta, 2);
16 }
17
18 public double getHipotenusa() {
19 return Math.sqrt(2 * Math.pow(aresta, 2));
20 }
21
22 public boolean equals(Object obj) {
23 if(!(obj instanceof Quadrado))
24 return false;
25
26 Quadrado outro = (Quadrado)obj;
27
28 return compareTo(outro) == 0;
29 }
30
31 public int compareTo(Quadrado outro) {
32 return this.aresta - outro.aresta;
33 }
34
35 public String toString() {
36 return
37 "[aresta=" + aresta +
38 "; area=" + getArea() +
39 "; hipotenusa=" + getHipotenusa() + "]"
40 ;
41 }
42 }
Alteramos agora a linha 1 para declarar que a nossa classe implementa a interface Comparable. Na linha 31, trocamos nosso método compararCom por uma implementação do método compareTo, previsto na interface Comparable (Na verdade trocamos apenas os nomes dos métodos… :lol: ).
Agora sim, se rodarmos novamente aquela classe TesteTreeSet, vai funcionar direitinho, dando a seguinte saída:
Quadrados desordenados:
[aresta=1; area=1; hipotenusa=1.4142135623730951]
[aresta=35; area=1225; hipotenusa=49.49747468305833]
[aresta=12; area=144; hipotenusa=16.97056274847714]
[aresta=35; area=1225; hipotenusa=49.49747468305833]
[aresta=12; area=144; hipotenusa=16.97056274847714]
[aresta=1; area=1; hipotenusa=1.4142135623730951]
Quadrados ordenados:
[aresta=1; area=1; hipotenusa=1.4142135623730951]
[aresta=12; area=144; hipotenusa=16.97056274847714]
[aresta=35; area=1225; hipotenusa=49.49747468305833]
Temos aí a listagem dos punhado de quadrados que tinhamos, e depois uma listagem do conjunto ordenado de quadrados, implementado pelo TreeSet.
Um outro exemplo de classe do Java que utiliza objetos Comparable, é a classe [i]Arrays/i. Ela é uma classe utilitária que tem vários métodos estáticos para auxiliar a manipulação de vetores. Um destes métodos é o sort, utilizado para ordenar um vetor. Uma das suas várias assinaturas é esta:
public static void sort(Object[])
Esta sobrecarga de sort exige que os elementos do vetor passado por parâmetro seja de uma classe que implemente a interface Comparable, senão ele vai dar o mesmo problema que vimos com o TreeSet…
Rodem o seguinte programa:
01 import java.util.Arrays;
02
03 public class TesteArrays {
04 public static void main(String[] args) {
05 /*
06 * Criamos um vetor de tipo Quadrado com um punhado de objetos Quadrado
07 * fora de ordem
08 */
09 Quadrado[] quadrados = {
10 new Quadrado(1),
11 new Quadrado(35),
12 new Quadrado(12),
13 new Quadrado(35),
14 new Quadrado(12),
15 new Quadrado(1),
16 };
17
18 System.out.println("Quadrados desordenados:");
19 for(Quadrado quadrado: quadrados) {
20 System.out.println("\t" + quadrado);
21 }
22 System.out.println();
23
24 /*
25 * Utilizamos o método sort de Arrays para ordenar o nosso vetor
26 * quadrados de acordo com a ordenação natural definida pela classe
27 * Quadrado
28 */
29 Arrays.sort(quadrados);
30
31 System.out.println("Quadrados ordenados:");
32 for(Quadrado quadrado: quadrados) {
33 System.out.println("\t" + quadrado);
34 }
35 }
36 }
O resultado esperedo é o seguinte:
Quadrados desordenados:
[aresta=1; area=1; hipotenusa=1.4142135623730951]
[aresta=35; area=1225; hipotenusa=49.49747468305833]
[aresta=12; area=144; hipotenusa=16.97056274847714]
[aresta=35; area=1225; hipotenusa=49.49747468305833]
[aresta=12; area=144; hipotenusa=16.97056274847714]
[aresta=1; area=1; hipotenusa=1.4142135623730951]
Quadrados ordenados:
[aresta=1; area=1; hipotenusa=1.4142135623730951]
[aresta=1; area=1; hipotenusa=1.4142135623730951]
[aresta=12; area=144; hipotenusa=16.97056274847714]
[aresta=12; area=144; hipotenusa=16.97056274847714]
[aresta=35; area=1225; hipotenusa=49.49747468305833]
[aresta=35; area=1225; hipotenusa=49.49747468305833]
Veja que o vetor quadrados foi alterado de forma a ter todos seus elementos postos em ordem, segundo os critérios implementados pelo método compareTo da classe Quadrado.
Mais uma coisa a se notar: Percebeu que as vezes chamamos um “objeto de classe a qual implementa Comparable” simplesmente de “objeto Comparable”? Isto traz uma carga semântica muito importante! “Comparable” é igual a “comparável” em inglês. Então, um objeto de uma classe a qual implementa a interface Comparable, é um objeto que pode ser comparado.
[size=18]Já a interface Comparator[/size], tem também o propósito de estabelecer a ordenação entre objetos, mas de uma forma um pouco diferente…
Imagine a seguinte classe:
01 public class Cliente implements Comparable<Cliente>{
02 private String
03 nome,
04 cpf
05 ;
06 private char sexo;
07 private double
08 peso,
09 altura
10 ;
11
12 //Getters e Setters omitidos
13
14 public boolean equals(Object obj) {
15 if(!(obj instanceof Cliente))
16 return false;
17
18 Cliente outro = (Cliente)obj;
19
20 return this.compareTo(outro) == 0;
21 }
22
23 public String toString() {
24 return
25 "[cpf=" + cpf +
26 "; nome=" + nome +
27 "; sexo=" + sexo +
28 "; peso=" + peso +
29 "; altura=" + altura + "]"
30 ;
31 }
32
33 public int compareTo(Cliente outro) {
34 if(outro == null)
35 throw new ClassCastException("null");
36
37 return this.cpf.compareTo(outro.cpf);
38 }
39 }
Ok! Até aqui, nada de novo. Mas e se quisermos estabelecer um outro critério de ordenação para os objetos Cliente? Por exemplo, como faríamos para ter um vetor de objetos Cliente, ordenado não pelo cpf, mas pelo nome?
Inicialmente, podemos simplesmente, no meio do nosso código, implementar algum algoritimo de ordenação e ordenar o vetor utilizando o método getNome dos objetos no vetor. O primeiro ponto ruim disso é que vai melecar o seu código.
Mas aí podemos pensar em criar uma classe só pra fazer tal ordenação. Legal! Mas vamos ter que continuar a implementar algum algoritmo de ordenação… Estariamos re-inventando a roda, por que o Java já tem na classe Arrays métodos para ordenação de vetores. Mas como que o Arrays vai saber que não queremos utilizar o critério da ordenação natural (ordenar por cpf), e sim, um outro critério (ordenação por nome)? É aí que entra em jogo a interface [i]Comparator/i!
Olhe só que interessante essa sobrecarga do método sort da classe Arrays:
public static <T> void sort(T[] a, Comparator<? super T> c)
Temos aí agora como parâmetro, além do vetor a ser ordenado, um objeto de uma classe a qual implemente a interface Comparator(um objeto Comparator). Qual é o papel da interface Comparator nesse método?
A interface Comparator declara dois métodos:
:arrow: public int compare(T, T)
:arrow: public boolean equals(Object)
Para este post, esqueça esse método equals da interface. Apenas tenha em mente que este equals não diz respeito aos objetos que comparados. Este equals é para verificar se dois objetos Comparator são equivalentes. Trata-se de um uso avançado, que não abordaremos aqui.
O contrato do método compare é semelhante ao do compareTo da interface Comparable. A diferença é que, enquanto o compareTo serve para comparar o próprio objeto que invoca o compareTo (this) com o objeto passado por parâmetro (outro), o método compare da interface Comparable serve para comparar o objeto do tipo T passado no primiero parâmetro com o objeto do tipo T passado no segundo parâmetro.
A classe que implementar a interface Comparator, terá que implementar o método compare, implementando nele algum critério de comparação entre objetos do tipo T, seja lá quem for T.
Isso é interessante para nós, pois temos aqui a chance de criar o tal vetor de clientes ordenado por nome. Podemos criar uma classe que implemente Comparator e escrever o método compare de tal forma, que estabeleça que
:arrow: Um objeto Cliente c1 é menor que um objeto Cliente c2, se o nome de c1 for “alfabeticamente menor” que o nome de c2. Neste caso, compare deve retornar um número estritamente negativo.
:arrow: Um objeto Cliente c1 é maior que um objeto Cliente c2, se o nome de c1 for “alfabeticamente maior” que o nome de c2. Neste caso, compare deve retornar um número estritamente positivo.
:arrow: Um objeto Cliente c1 é igual a um objeto Cliente c2, se o nome de c1 for “alfabeticamente igual” ao nome de c2. Neste caso, compare deve retornar zero.
Vamos ver como ficaria esse Comparator:
01 import java.util.Comparator;
02
03 public class ClienteNomeComparator implements Comparator<Cliente> {
04 public int compare(Cliente c1, Cliente c2) {
05 if(c1 == null || c2 == null)
06 throw new ClassCastException("null");
07
08 String
09 nome1 = c1.getNome(),
10 nome2 = c2.getNome()
11 ;
12
13 /*
14 * Aproveitamo-nos do fato de a classe String implementar Comparable,
15 * evitando que façamos todo o trabalho duro para verificar se um String
16 * é alfabéticamente maior ou menor que outro.
17 */
18 return nome1.compareTo(nome2);
19 }
20 }
Agora, vamos criar uma classe de teste que vai criar um vetor de clientes e imprimila 3 vezes: desordenada, ordenada por nome e ordenada naturalmente (por cpf):
01 import java.util.Arrays;
02
03 public class TesteClienteArrays {
04 public static void main(String[] args) {
05 Cliente[] clientes = {
06 new Cliente("Antonin Dvorak", "745.333.560-44", 'M', 67.38, 1.65),
07 new Cliente("Piotr Yilitch Tchaikowiski", "764.099.230-65", 'M', 53.22, 1.90),
08 new Cliente("Joao da Silva", "000.000.001-99", 'M', 75.16, 1.75),
09 new Cliente("Gustav Mahler", "000.000.001-99", 'M', 51.27, 1.87),
10 new Cliente("Joao da Silva", "000.000.002-75", 'M', 66.16, 1.64),
11 };
12
13 System.out.println("Lista desordenada de clientes:");
14 for (int c = 0; c < clientes.length; c++) {
15 System.out.println("\t" + clientes[c]);
16 }
17 System.out.println();
18
19 /*
20 * Utilizando sort(T, Comparator<? super T>)
21 */
22 ClienteNomeComparator comparadorPorNome = new ClienteNomeComparator();
23 Arrays.sort(clientes, comparadorPorNome);
24
25 System.out.println("Lista de clientes por nome:");
26 for (int c = 0; c < clientes.length; c++) {
27 System.out.println("\t" + clientes[c]);
28 }
29 System.out.println();
30
31 /*
32 * Utilizando sort(Object[])
33 */
34 Arrays.sort(clientes);
35
36 System.out.println("Lista de clientes por cpf(natural):");
37 for (int c = 0; c < clientes.length; c++) {
38 System.out.println("\t" + clientes[c]);
39 }
40 }
41 }
Na linha 23, estamos ordenando o vetor clientes com o sort que usa um Comparator para realizar a ordenação. Internamente, sempre que ele precisar comparar alguma elemento e1 com algum outro elemento e2 do vetor, ele vai fazer algo parecido com isto:
int resultadoComparacao =
objetoComparatorPassadoPorParametro.compare(e1, e2);
//Faz alguma coisa com esse resultado
A saída deste programa deve ser a seguinte:
Lista desordenada de clientes:
[cpf=745.333.560-44; nome=Antonin Dvorak; sexo=M; peso=67.38; altura=1.65]
[cpf=764.099.230-65; nome=Piotr Yilitch Tchaikowiski; sexo=M; peso=53.22; altura=1.9]
[cpf=000.000.001-99; nome=Joao da Silva; sexo=M; peso=75.16; altura=1.75]
[cpf=000.000.001-99; nome=Gustav Mahler; sexo=M; peso=51.27; altura=1.87]
[cpf=000.000.002-75; nome=Joao da Silva; sexo=M; peso=66.16; altura=1.64]
Lista de clientes por nome:
[cpf=745.333.560-44; nome=Antonin Dvorak; sexo=M; peso=67.38; altura=1.65]
[cpf=000.000.001-99; nome=Gustav Mahler; sexo=M; peso=51.27; altura=1.87]
[cpf=000.000.001-99; nome=Joao da Silva; sexo=M; peso=75.16; altura=1.75]
[cpf=000.000.002-75; nome=Joao da Silva; sexo=M; peso=66.16; altura=1.64]
[cpf=764.099.230-65; nome=Piotr Yilitch Tchaikowiski; sexo=M; peso=53.22; altura=1.9]
Lista de clientes por cpf(natural):
[cpf=000.000.001-99; nome=Gustav Mahler; sexo=M; peso=51.27; altura=1.87]
[cpf=000.000.001-99; nome=Joao da Silva; sexo=M; peso=75.16; altura=1.75]
[cpf=000.000.002-75; nome=Joao da Silva; sexo=M; peso=66.16; altura=1.64]
[cpf=745.333.560-44; nome=Antonin Dvorak; sexo=M; peso=67.38; altura=1.65]
[cpf=764.099.230-65; nome=Piotr Yilitch Tchaikowiski; sexo=M; peso=53.22; altura=1.9]
Um outro uso é criar uma classe que implemente Comparator para estabelecer uma ordenação entre objetos de uma classe a qual não define uma ordenação natural (não implementa Comparable).
A classe TreeSet tem também um contrutor que recebe um objeto Comparator por parâmetro. É uma boa saída para criar conjuntos do objetos não-comparáveis naturalmente. Só fique atento para o seguinte: Nesses casos, nem sempre é fácil ou mesmo possível manter uma consistência entre os métodos equals e o compare. Você deve analisar qual o possível impacto que isso possa ter no seu programa.
Bom, Acho que é isso.
Qualquer dúvida ou correção, postem mais.
Valeu!