¿Por qué debería una clase Java implementar comparable?

Respuestas:

212

Aquí hay una muestra de la vida real. Tenga en cuenta que Stringtambién se 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;
    }
}

luego..

/**
 * 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
fuente
15
Solo quiero señalar que a menudo querrá anular equals(y, por lo tanto hashCode) para ser coherente con su compareTométodo. Por ejemplo, esto es necesario si quieres que la clase juegue bien con a TreeSet.
pidge
¿Por qué no simplemente regresar last?
Anirban Nag 'tintinmj'
@ AnirbanNag'tintinmj 'para ordenar automáticamente por el nombre en caso de que el apellido sea el mismo.
OddDev
Más uno por la buena explicación de por qué compareTo devuelve un int y lo que significa. Más útil.
james.garriss
1
@ user3932000: Correcto, ese es prácticamente el punto de todas las interfaces. ¡Pero tenga en cuenta que "otros métodos Java" incluye métodos escritos por el usuario! De hecho, diría que el código del usuario consume la mayoría de las interfaces. En bases de código más grandes, "usted mismo" se convierte rápidamente en "otros"
Enno Shioji
40

Comparable define un ordenamiento natural. Lo que esto significa es que lo está definiendo cuando un objeto debe considerarse "menor que" o "mayor que".

Supongamos que tiene un montón de enteros y desea ordenarlos. Eso es bastante fácil, solo póngalos en una colección ordenada, ¿verdad?

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

Pero ahora supongamos que tengo algún objeto personalizado, donde la ordenación tiene sentido para mí, pero no está definida. Digamos que tengo datos que representan distritos por código postal con densidad de población, y quiero ordenarlos por densidad:

public class District {
  String zipcode; 
  Double populationDensity;
}

Ahora, la forma más fácil de ordenarlos es definirlos con un orden natural mediante la implementación de Comparable, lo que significa que hay una forma estándar de definir estos objetos para que se ordenen:

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

Tenga en cuenta que puede hacer lo equivalente definiendo un comparador. La diferencia es que el comparador define la lógica de ordenamiento fuera del objeto . Quizás en un proceso separado necesito ordenar los mismos objetos por código postal; en ese caso, el orden no es necesariamente una propiedad del objeto, o difiere del orden natural de los objetos. Puede usar un comparador externo para definir un orden personalizado en enteros, por ejemplo, ordenándolos por su valor alfabético.

Básicamente, la lógica de ordenamiento tiene que existir en alguna parte. Eso puede ser -

  • en el objeto en sí, si es naturalmente comparable (extiende enteros -eg enteros)

  • suministrado en un comparador externo, como en el ejemplo anterior.

Steve B.
fuente
buen ejemplo, pero tiene que ser en TreeSet<Integer>lugar de TreeMap<Integer>, ya que este último no existe, los TreeMaps siempre son <Key,Value>pares. Por cierto, un hipotético TreeMap<District, Object>solo funcionaría si el Distrito implementara Comparable, ¿verdad? Todavía trato de entender esto
phil294
14

Citado del javadoc;

Esta interfaz impone un orden total en los objetos de cada clase que lo implementa. Este orden se conoce como el orden natural de la clase, y el método compareTo de la clase se conoce como su método de comparación natural.

Las listas (y matrices) de objetos que implementan esta interfaz se pueden ordenar automáticamente por Collections.sort (y Arrays.sort). Los objetos que implementan esta interfaz se pueden usar como claves en un mapa ordenado o como elementos en un conjunto ordenado, sin la necesidad de especificar un comparador.

Editar: ... e hizo que la parte importante fuera audaz.

Qwerky
fuente
44
Yo diría que la oración después de la que has marcado es igual de importante (si no más).
Michael Borgwardt
8

El hecho de que una clase se implemente Comparablesignifica que puede tomar dos objetos de esa clase y compararlos. Algunas clases, como ciertas colecciones (función de clasificación en una colección) que mantienen los objetos en orden, se basan en que sean comparables (para clasificar, necesita saber qué objeto es el "más grande", etc.).

Amir Rachum
fuente
8

La mayoría de los ejemplos anteriores muestran cómo reutilizar un objeto comparable existente en la función compareTo. Si desea implementar su propio compareTo cuando quiera comparar dos objetos de la misma clase, diga un objeto AirlineTicket que le gustaría ordenar por precio (menos se clasifica primero), seguido por el número de escala (nuevamente, menos es clasificado primero), haría lo siguiente:

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
fuente
6

Una manera fácil de implementar comparaciones de múltiples campos es con la comparación de Guava's Cadena , entonces puede decir

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

en vez 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
fuente
3

Por ejemplo, cuando desea tener una colección o mapa ordenado

Fernando Miguélez
fuente
Lo que quiere decir Fernando es: si almacena "cosas" que implementan Comparable en una clase de contenedor ordenado, la clase de contenedor ordenado puede ordenar automáticamente esas "cosas".
Ian Durkan
2

Comparable se utiliza para comparar instancias de su clase. Podemos comparar instancias de muchas maneras, por eso necesitamos implementar un método compareTopara saber cómo (atributos) queremos comparar instancias.

Dog clase:

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 clase:

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
         */

    }
}

Aquí hay un buen ejemplo de cómo usar comparable en Java:

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

no no
fuente
2

Cuando implementa la Comparableinterfaz, necesita implementar el método compareTo(). Lo necesita para comparar objetos, para utilizar, por ejemplo, el método de clasificación de la ArrayListclase. Necesita una forma de comparar sus objetos para poder ordenarlos. Por lo tanto, necesita un compareTo()método personalizado en su clase para poder usarlo con el ArrayListmétodo de clasificación. El compareTo()método devuelve -1,0,1.

Acabo de leer un capítulo correspondiente en Java Head 2.0, todavía estoy aprendiendo.

lxknvlk
fuente
1

Bien, pero ¿por qué no solo definir un compareTo()método sin implementar una interfaz comparable? Por ejemplo, una clase Citydefinida por su namey temperaturey

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
fuente
Esto no funciona. Sin implementar comparable - obtengo una excepción de difusión de clase
Karan Ahuja