Por que uma classe Java deve implementar comparável?

Respostas:

212

Aqui está uma amostra da vida real. Observe que Stringtambém implementa Comparable.

class Author implements Comparable<Author>{
    String firstName;
    String lastName;

    @Override
    public int compareTo(Author other){
        // compareTo should return < 0 if this is supposed to be
        // less than other, > 0 if this is supposed to be greater than 
        // other and 0 if they are supposed to be equal
        int last = this.lastName.compareTo(other.lastName);
        return last == 0 ? this.firstName.compareTo(other.firstName) : last;
    }
}

mais tarde..

/**
 * List the authors. Sort them by name so it will look good.
 */
public List<Author> listAuthors(){
    List<Author> authors = readAuthorsFromFileOrSomething();
    Collections.sort(authors);
    return authors;
}

/**
 * List unique authors. Sort them by name so it will look good.
 */
public SortedSet<Author> listUniqueAuthors(){
    List<Author> authors = readAuthorsFromFileOrSomething();
    return new TreeSet<Author>(authors);
}
Enno Shioji
fonte
15
Eu só quero observar que muitas vezes você deseja substituir equals(e, portanto hashCode) ser consistente com seu compareTométodo. Por exemplo, isso é necessário se você quiser que a turma seja agradável com a TreeSet.
Pidge
Por que não simplesmente voltar last?
Anirban Nag 'tintinmj'
@ AnirbanNag'tintinmj 'para classificar automaticamente pelo primeiro nome, caso o sobrenome seja o mesmo.
OddDev 5/08/15
Mais uma pela boa explicação de por que compareTo retorna um int e o que isso significa. Mais útil.
James.garriss
1
@ user3932000: Certo, esse é o objetivo de todas as interfaces. Mas observe que "outros métodos Java" incluem métodos escritos pelo usuário! Na verdade, eu diria que a maioria das interfaces é consumida pelo código do usuário. Em bases de código maiores, "se" rapidamente se torna "outros"
Enno Shioji
40

Comparável define uma ordem natural. O que isso significa é que você o define quando um objeto deve ser considerado "menor que" ou "maior que".

Suponha que você tenha vários números inteiros e queira classificá-los. Isso é bem fácil, basta colocá-los em uma coleção ordenada, certo?

TreeSet<Integer> m = new TreeSet<Integer>(); 
m.add(1);
m.add(3);
m.add(2);
for (Integer i : m)
... // values will be sorted

Mas agora suponha que eu tenha algum objeto personalizado, em que a classificação faça sentido para mim, mas seja indefinida. Digamos, eu tenho dados representando distritos por CEP com densidade populacional e quero classificá-los por densidade:

public class District {
  String zipcode; 
  Double populationDensity;
}

Agora, a maneira mais fácil de classificá-las é defini-las com uma ordem natural implementando Comparable, o que significa que existe uma maneira padrão de definir esses objetos para serem solicitados .:

public class District implements Comparable<District>{
  String zipcode; 
  Double populationDensity;
  public int compareTo(District other)
  {
    return populationDensity.compareTo(other.populationDensity);
  }
}

Observe que você pode fazer o equivalente definindo um comparador. A diferença é que o comparador define a lógica de pedidos fora do objeto . Talvez em um processo separado eu precise ordenar os mesmos objetos por CEP - nesse caso, a ordem não é necessariamente uma propriedade do objeto ou difere da ordem natural dos objetos. Você pode usar um comparador externo para definir uma ordem personalizada em números inteiros, por exemplo, classificando-os por seu valor alfabético.

Basicamente, a lógica de pedidos deve existir em algum lugar. Isso pode ser -

  • no próprio objeto, se for naturalmente comparável (estende Comparable -eg inteiros)

  • fornecido em um comparador externo, como no exemplo acima.

Steve B.
fonte
bom exemplo, mas deve ser em TreeSet<Integer>vez de TreeMap<Integer>, pois o último não existe, os TreeMaps são sempre <Key,Value>-pairs. Aliás, uma hipótese TreeMap<District, Object>só funcionaria se o Distrito implementasse o Comparable, certo? Ainda tentando entender isso
phil294
14

Citado do javadoc;

Essa interface impõe uma ordem total aos objetos de cada classe que a implementa. Essa ordem é chamada de ordem natural da classe e o método compareTo da classe é chamado de método de comparação natural.

As listas (e matrizes) de objetos que implementam essa interface podem ser classificadas automaticamente por Collections.sort (e Arrays.sort). Objetos que implementam essa interface podem ser usados ​​como chaves em um mapa classificado ou como elementos em um conjunto classificado, sem a necessidade de especificar um comparador.

Edit: ..e deixou o importante em negrito.

Qwerky
fonte
4
Eu diria que a frase após a que você colocou em negrito é tão (se não mais) importante.
Michael Borgwardt
8

O fato de uma classe implementar Comparablesignifica que você pode pegar dois objetos dessa classe e compará-los. Algumas classes, como determinadas coleções (função de classificação em uma coleção), que mantêm os objetos em ordem, dependem deles para serem comparáveis ​​(para classificar, você precisa saber qual objeto é o "maior" e assim por diante).

Amir Rachum
fonte
8

A maioria dos exemplos acima mostra como reutilizar um objeto comparável existente na função compareTo. Se você deseja implementar seu próprio compareTo quando desejar comparar dois objetos da mesma classe, diga um objeto AirlineTicket que você gostaria de classificar por preço (menos é classificado primeiro), seguido pelo número de escala (novamente, menos é em primeiro lugar), você faria o seguinte:

class AirlineTicket implements Comparable<Cost>
{
    public double cost;
    public int stopovers;
    public AirlineTicket(double cost, int stopovers)
    {
        this.cost = cost; this.stopovers = stopovers ;
    }

    public int compareTo(Cost o)
    {
        if(this.cost != o.cost)
          return Double.compare(this.cost, o.cost); //sorting in ascending order. 
        if(this.stopovers != o.stopovers)
          return this.stopovers - o.stopovers; //again, ascending but swap the two if you want descending
        return 0;            
    }
}
arviman
fonte
6

Uma maneira fácil de implementar várias comparações de campo é com o ComparisonChain da Guava - então você pode dizer

   public int compareTo(Foo that) {
     return ComparisonChain.start()
         .compare(lastName, that.lastName)
         .compare(firstName, that.firstName)
         .compare(zipCode, that.zipCode)
         .result();
   }

ao invés de

  public int compareTo(Person other) {
    int cmp = lastName.compareTo(other.lastName);
    if (cmp != 0) {
      return cmp;
    }
    cmp = firstName.compareTo(other.firstName);
    if (cmp != 0) {
      return cmp;
    }
    return Integer.compare(zipCode, other.zipCode);
  }
}
Ran Adler
fonte
3

Por exemplo, quando você deseja ter uma coleção ou mapa classificado

Fernando Miguélez
fonte
O que Fernando quer dizer é: se você armazenar "coisas" que implementam Comparable em uma classe de contêiner classificado, a classe de contêiner classificado pode ordenar automaticamente essas "coisas".
30512 Ian Durkan
2

Comparable é usado para comparar instâncias da sua classe. Podemos comparar instâncias de várias maneiras, e é por isso que precisamos implementar um método compareTopara saber como (atributos) queremos comparar instâncias.

Dog classe:

package test;
import java.util.Arrays;

public class Main {

    public static void main(String[] args) {
        Dog d1 = new Dog("brutus");
        Dog d2 = new Dog("medor");
        Dog d3 = new Dog("ara");
        Dog[] dogs = new Dog[3];
        dogs[0] = d1;
        dogs[1] = d2;
        dogs[2] = d3;

        for (int i = 0; i < 3; i++) {
            System.out.println(dogs[i].getName());
        }
        /**
         * Output:
         * brutus
         * medor
         * ara
         */

        Arrays.sort(dogs, Dog.NameComparator);
        for (int i = 0; i < 3; i++) {
            System.out.println(dogs[i].getName());
        }
        /**
         * Output:
         * ara
         * medor
         * brutus
         */

    }
}

Main classe:

package test;

import java.util.Arrays;

public class Main {

    public static void main(String[] args) {
        Dog d1 = new Dog("brutus");
        Dog d2 = new Dog("medor");
        Dog d3 = new Dog("ara");
        Dog[] dogs = new Dog[3];
        dogs[0] = d1;
        dogs[1] = d2;
        dogs[2] = d3;

        for (int i = 0; i < 3; i++) {
            System.out.println(dogs[i].getName());
        }
        /**
         * Output:
         * brutus
         * medor
         * ara
         */

        Arrays.sort(dogs, Dog.NameComparator);
        for (int i = 0; i < 3; i++) {
            System.out.println(dogs[i].getName());
        }
        /**
         * Output:
         * ara
         * medor
         * brutus
         */

    }
}

Aqui está um bom exemplo de como usar comparável em Java:

http://www.onjava.com/pub/a/onjava/2003/03/12/java_comp.html?page=2

não não
fonte
2

Quando você implementa Comparableinterface, você precisa implementar o método compareTo(). Você precisa comparar objetos, para usar, por exemplo, o método de classificação da ArrayListclasse. Você precisa de uma maneira de comparar seus objetos para poder classificá-los. Portanto, você precisa de um compareTo()método personalizado em sua classe para poder usá-lo com o ArrayListmétodo de classificação. O compareTo()método retorna -1,0,1.

Acabei de ler um capítulo correspondente no Java Head 2.0, ainda estou aprendendo.

lxknvlk
fonte
1

OK, mas por que não apenas definir um compareTo()método sem implementar uma interface comparável. Por exemplo, uma classe Citydefinida pela sua namee temperaturee

public int compareTo(City theOther)
{
    if (this.temperature < theOther.temperature)
        return -1;
    else if (this.temperature > theOther.temperature)
        return 1;
    else
        return 0;
}
Ale B
fonte
Isso não funciona. Sem a implementação comparável - Recebo exceção classcast
Karan Ahuja