Interfaz Comparable
Comparable es una interfaz impone un ordenamiento total a los objetos de cada clase que lo implementa. Este orden se conoce como ordenamiento natural 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 mediante Collections.sort() (y Arrays.sort). Los objetos que implementan esta interfaz se pueden utilizar como claves en un mapa ordenado o como elementos en un conjunto ordenado, sin necesidad de especificar un comparador.
- El número 0 se devuelve cuando el objeto actual es equivalente al argumento de compareTo().
- Se devuelve un número negativo cuando el objeto actual es más pequeño que el argumento de compareTo().
- Se devuelve un número positivo cuando el objeto actual es mayor que el argumento de compareTo().
En este caso, la clase Animal, la cual implementa la interfaz Comparable, cuenta con dos atributos: species y weight. En el caso de tener un solo atributo, el método compareTo() haría la comparación con respecto a ese único atributo, pero en este caso, tenemos varias posibilidades:
- Si en la primera instancia de Animal y la segunda instancia el valor de species es distinto, la comparación se hará únicamente entre los valores de ese atributo. En este caso no importa los valores del segundo atributo.
- Si en la primera instancia de Animal y en la segunda instancia el valor de species es igual, la comparación pasará a hacerse con el segundo atributo (como sucede en el ejemplo). Si este atributo es igual en ambas instancias, entonces la comparación se hará respecto a este último, devolviendo como resultado 0.
import java.util.*;
public class Animal implements Comparable<Animal> {
private String species;
private int weight;
public Animal(String species, int weight) {
this.species = species;
this.weight = weight;
}
public String toString() {
return species;
}
public int compareTo(Animal otherAnimal) {
int result = this.getSpecies().compareTo(otherAnimal.getSpecies());
if (result != 0) {
return result;
}
else {
return this.getWeight()-otherAnimal.getWeight();
}
}
public String getSpecies() {
return this.species;
}
public int getWeight() {
return this.weight;
}
public static void main(String[] args) {
Animal a1 = new Animal("Bird",20);
Animal a2 = new Animal("Bird",50);
System.out.println(a1.compareTo(a2)); // -30
System.out.println(a1.compareTo(a1)); // 0
System.out.println(a2.compareTo(a1)); // 30
}
}
En el ejemplo de abajo, el método compareTo() de la clase MissingAnimal está preparado para controlar el caso en el que el valor del atributo name sea nulo, tanto de manera interna como externa (es decir, en el que caso de que a1.name sea nulo o en el que a2.name sea nulo).
public class MissingAnimal implements Comparable<MissingAnimal> {
private String name;
public MissingAnimal(String name) {
this.name = name;
}
public int compareTo(MissingAnimal missingAnimal) {
if (missingAnimal == null) throw new IllegalArgumentException("Poorly formed animal!");
if (this.name == null && missingAnimal.name == null) return 0;
else if (this.name == null) return -1;
else if (missingAnimal.name == null) return 1;
else return name.compareTo(missingAnimal.name);
}
public static void main(String[] args) {
MissingAnimal a1 = new MissingAnimal("Pato");
MissingAnimal a2 = new MissingAnimal(null);
System.out.println(a1.compareTo(a2));
System.out.println(a1.compareTo(a1));
System.out.println(a2.compareTo(a1));
}
}
Si una clase implementa Comparable, introduce una nueva lógica para determinar la igualdad. Es importante que las clases Comparable sean consistentes con equals porque algunas colecciones no funcionan correctamente si compareTo() y equals() no son consistentes. Un orden natural es consistente con equals si x.equals(y) es verdadero siempre que x.compareTo(y) sea 0, y x.equals(y) es falso siempre que x.compareTo(y) no sea 0. Por ejemplo, la siguiente clase Product tiene un compareTo() que no es consistente con equals.
public class Product implements Comparable<Product> {
private int id;
private String name;
public Product(int id, String name) {
this.id = id;
this.name = name;
}
public int hashCode() { return id; }
public boolean equals(Object obj) {
if(!(obj instanceof Product)) return false;
Product other = (Product) obj;
return this.id == other.id;
}
public int compareTo(Product obj) {
return this.name.compareTo(obj.name);
}
public static void main(String[] args) {
Product p1 = new Product(1, "Pan");
Product p2 = new Product(2, "Queso");
System.out.println("compareTo()");
System.out.println(p1.compareTo(p2));
System.out.println(p1.compareTo(p1));
System.out.println(p2.compareTo(p1));
System.out.println("equals()");
System.out.println(p1.equals(p2));
System.out.println(p1.equals(p1));
System.out.println(p2.equals(p1));
}
}
Este código muestra como comprar dos instancias de la clase Animal de diferentes maneras. La clase Animal cuenta con los atributos species y weight, y su método compareTo() ordena primero por species y luego, si el primer atributo es igual en ambas intancias, por peso. En el método main(), se crea un Comparator anónimo que ordena las instancias de Animal solo por weight, y se realizan comparaciones utilizando este Comparator, mostrando así la flexibilidad para ordenar objetos de distintas formas según sea necesario.
import java.util.*;
public class Animal implements Comparable<Animal> {
private String species;
private int weight;
public Animal(String species, int weight) {
this.species = species;
this.weight = weight;
}
public String getSpecies() {
return this.species;
}
public int getWeight() {
return this.weight;
}
public String toString() {
return species;
}
public int compareTo(Animal otherAnimal) {
int result = this.getSpecies().compareTo(otherAnimal.getSpecies());
if (result != 0) {
return result;
}
else {
return this.getWeight()-otherAnimal.getWeight();
}
}
public static void main(String[] args) {
Comparator<Animal> byWeight = new Comparator<Animal>() {
public int compare(Animal lefta, Animal righta) {
return lefta.getWeight() - righta.getWeight();
}
};
Animal a1 = new Animal("Bird",20);
Animal a2 = new Animal("Bird",50);
System.out.println("With compareTo():");
System.out.println(a1.compareTo(a2));
System.out.println(a1.compareTo(a1));
System.out.println(a2.compareTo(a1));
System.out.println("With Comparator:");
System.out.println(byWeight.compare(a1,a2));
System.out.println(byWeight.compare(a1,a1));
System.out.println(byWeight.compare(a2,a1));
}
}
NaftalinWadler-JavaGenericsAndCollections page 33-34
Look Out for This! It’sworth pointing out a subtlety in the definition of comparison. Here is the right way to compare two integers:
class Integer implements Comparable
{ ... public int compareTo(Integer that) { return this.value < that.value ? -1 : this.value == that.value ? 0 : 1 ; } ... } The conditional expression returns – 1, 0, or 1 depending on whether the receiver is less than, equal to, or greater than the argument. You might think the following code would work just as well, since the method is permitted to return any negative integer if the receiver is less than the argument:
class Integer implements Comparable
{ ... public int compareTo(Integer that) { // bad implementation -- don't do it this way! return this.value - that.value; } ... } But this code may give the wrong answer when there is overflow. For instance, when comparing a large negative value to a large positive value, the difference may be more than the largest value that can be stored in an integer,
Integer.MAX_VALUE
.