Buscar..


Sintaxis

  • clase pública implementa MyClass Comparable <MyClass >
  • la clase pública MyComparator implementa Comparator <SomeOtherClass >
  • public int compareTo (MyClass other)
  • public int compare (SomeOtherClass o1, SomeOtherClass o2)

Observaciones

Al implementar un compareTo(..) que depende de un double , no haga lo siguiente:

public int comareTo(MyClass other) {
    return (int)(doubleField - other.doubleField); //THIS IS BAD
}

El truncamiento causado por (int) cast causará que el método a veces devuelva incorrectamente 0 lugar de un número positivo o negativo, y por lo tanto puede llevar a errores de comparación y clasificación.

En su lugar, la implementación correcta más simple es usar Double.compare , como tal:

public int comareTo(MyClass other) {
    return Double.compare(doubleField,other.doubleField); //THIS IS GOOD
} 

Una versión no genérica de Comparable<T> , simplemente Comparable , ha existido desde Java 1.2 . Además de interactuar con el código heredado, siempre es mejor implementar la versión genérica Comparable<T> , ya que no requiere conversión en la comparación.


Es muy estándar que una clase sea comparable a sí misma, como en:

public class A implements Comparable<A>

Si bien es posible romper con este paradigma, tenga cuidado al hacerlo.


Un Comparator<T> todavía se puede usar en instancias de una clase si esa clase implementa Comparable<T> . En este caso, se utilizará la lógica del Comparator ; El orden natural especificado por la implementación Comparable será ignorado.

Ordenar una lista usando Comparable o un comparador

Digamos que estamos trabajando en una clase que representa a una persona por su nombre y apellido. Hemos creado una clase básica para hacer esto e implementado los métodos equals y hashCode adecuados.

public class Person {

    private final String lastName; //invariant - nonnull
    private final String firstName; //invariant - nonnull

    public Person(String firstName, String lastName){
        this.firstName = firstName != null ? firstName : "";
        this.lastName = lastName != null ? lastName : "";
    }

    public String getFirstName() {
        return firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public String toString() {
        return lastName + ", " + firstName;
    }

    @Override
    public boolean equals(Object o) {
        if (! (o instanceof Person)) return false;
        Person p = (Person)o;
        return firstName.equals(p.firstName) && lastName.equals(p.lastName);
    }

    @Override
    public int hashCode() {
        return Objects.hash(firstName, lastName);
    }
}

Ahora nos gustaría ordenar una lista de objetos Person por su nombre, como en el siguiente escenario:

public static void main(String[] args) {
    List<Person> people = Arrays.asList(new Person("John", "Doe"),
                                     new Person("Bob", "Dole"),
                                     new Person("Ronald", "McDonald"),
                                     new Person("Alice", "McDonald"),
                                     new Person("Jill", "Doe"));
    Collections.sort(people); //This currently won't work.
}

Desafortunadamente, como está marcado, lo anterior actualmente no compilará. Collections.sort(..) solo sabe cómo ordenar una lista si los elementos de esa lista son comparables o si se proporciona un método personalizado de comparación.

Si le pidieran que clasificara la siguiente lista: 1,3,5,4,2 , no tendría ningún problema en decir que la respuesta es 1,2,3,4,5 . Esto se debe a que los enteros (tanto en Java como matemáticamente) tienen un orden natural , un orden de comparación estándar predeterminado. Para dar a nuestra clase Person un orden natural, implementamos la Comparable<Person> , que requiere la implementación del método compareTo(Person p):

public class Person implements Comparable<Person> {

    private final String lastName; //invariant - nonnull
    private final String firstName; //invariant - nonnull

    public Person(String firstName, String lastName) {
        this.firstName = firstName != null ? firstName : "";
        this.lastName = lastName != null ? lastName : "";
    }

    public String getFirstName() {
        return firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public String toString() {
        return lastName + ", " + firstName;
    }

    @Override
    public boolean equals(Object o) {
        if (! (o instanceof Person)) return false;
        Person p = (Person)o;
        return firstName.equals(p.firstName) && lastName.equals(p.lastName);
    }

    @Override
    public int hashCode() {
        return Objects.hash(firstName, lastName);
    }

    @Override
    public int compareTo(Person other) {
        // If this' lastName and other's lastName are not comparably equivalent,
        // Compare this to other by comparing their last names.
        // Otherwise, compare this to other by comparing their first names
        int lastNameCompare = lastName.compareTo(other.lastName);
        if (lastNameCompare != 0) {
            return lastNameCompare;
        } else {
            return firstName.compareTo(other.firstName);
        }
    }
}

Ahora, el método principal dado funcionará correctamente.

public static void main(String[] args) {
    List<Person> people = Arrays.asList(new Person("John", "Doe"),
                                     new Person("Bob", "Dole"),
                                     new Person("Ronald", "McDonald"),
                                     new Person("Alice", "McDonald"),
                                     new Person("Jill", "Doe"));
    Collections.sort(people); //Now functions correctly

    //people is now sorted by last name, then first name:
    // --> Jill Doe, John Doe, Bob Dole, Alice McDonald, Ronald McDonald
}

Sin embargo, si no desea o no puede modificar la clase Person , puede proporcionar un Comparator<T> personalizado Comparator<T> que maneje la comparación de cualquiera de los dos objetos Person . Si le pidieran que clasificara la siguiente lista: circle, square, rectangle, triangle, hexagon no podría, pero si le pidieran que clasificara esa lista según el número de esquinas , podría hacerlo. De este modo, al proporcionar un comparador se le indica a Java cómo comparar dos objetos que normalmente no son comparables.

public class PersonComparator implements Comparator<Person> {

    public int compare(Person p1, Person p2) {
        // If p1's lastName and p2's lastName are not comparably equivalent,
        // Compare p1 to p2 by comparing their last names.
        // Otherwise, compare p1 to p2 by comparing their first names
        if (p1.getLastName().compareTo(p2.getLastName()) != 0) {
            return p1.getLastName().compareTo(p2.getLastName());
        } else {
            return p1.getFirstName().compareTo(p2.getFirstName());
        }
    }
}

//Assume the first version of Person (that does not implement Comparable) is used here
public static void main(String[] args) {
    List<Person> people = Arrays.asList(new Person("John", "Doe"),
                                     new Person("Bob", "Dole"),
                                     new Person("Ronald", "McDonald"),
                                     new Person("Alice", "McDonald"),
                                     new Person("Jill", "Doe"));
    Collections.sort(people); //Illegal, Person doesn't implement Comparable.
    Collections.sort(people, new PersonComparator()); //Legal

    //people is now sorted by last name, then first name:
    // --> Jill Doe, John Doe, Bob Dole, Alice McDonald, Ronald McDonald
}

Los comparadores también se pueden crear / usar como una clase interna anónima

//Assume the first version of Person (that does not implement Comparable) is used here
public static void main(String[] args) {
    List<Person> people = Arrays.asList(new Person("John", "Doe"),
                                     new Person("Bob", "Dole"),
                                     new Person("Ronald", "McDonald"),
                                     new Person("Alice", "McDonald"),
                                     new Person("Jill", "Doe"));
    Collections.sort(people); //Illegal, Person doesn't implement Comparable.

    Collections.sort(people, new PersonComparator()); //Legal

    //people is now sorted by last name, then first name:
    // --> Jill Doe, John Doe, Bob Dole, Alice McDonald, Ronald McDonald

    //Anonymous Class
    Collections.sort(people, new Comparator<Person>() { //Legal
        public int compare(Person p1, Person p2) {
            //Method code...
        }
    });
}
Java SE 8

Comparadores basados ​​en expresiones Lambda

A partir de Java 8, los comparadores también se pueden expresar como expresiones lambda

    //Lambda
    Collections.sort(people, (p1, p2) -> { //Legal
        //Method code....
    });

Métodos predeterminados del comparador

Por otra parte, existen métodos predeterminados interesantes en la interfaz de comparación para la construcción de comparadores: la siguiente construye un comparador compara por lastName y luego firstName .

Collections.sort(people, Comparator.comparing(Person::getLastName)
                                .thenComparing(Person::getFirstName));

Invertir el orden de un comparador

Cualquier comparador también puede revertirse fácilmente usando el método reversedMethod que cambiará el orden ascendente a descendente.

Los métodos de comparar y comparar

La interfaz Comparable<T> requiere un método:

public interface Comparable<T> {

    public int compareTo(T other);

}

Y la interfaz del Comparator<T> requiere un método:

public interface Comparator<T> {

    public int compare(T t1, T t2);

}

Estos dos métodos hacen esencialmente lo mismo, con una pequeña diferencia: compareTo compara this con other , mientras que compare compara t1 con t2 , sin preocuparse en absoluto por this .

Aparte de esa diferencia, los dos métodos tienen requisitos similares. Específicamente (para compareTo), compara este objeto con el objeto especificado para orden. Devuelve un entero negativo, cero o un entero positivo, ya que este objeto es menor, igual o mayor que el objeto especificado. Así, para la comparación de a y b :

  • Si a < b , a.compareTo(b) y compare(a,b) debe devolver un entero negativo, y b.compareTo(a) y compare(b,a) deben devolver un entero positivo
  • Si a > b , a.compareTo(b) y compare(a,b) debe devolver un entero positivo, y b.compareTo(a) y compare(b,a) deben devolver un entero negativo
  • Si a es igual a b para la comparación, todas las comparaciones deben devolver 0 .

Clasificación natural (comparable) vs explícita (comparativa)

Hay dos métodos Collections.sort() :

  • Uno que toma una List<T> como parámetro donde T debe implementar Comparable y anular el compareTo() que determina el orden de clasificación.
  • Uno que toma una Lista y un Comparador como los argumentos, donde el Comparador determina el orden de clasificación.

Primero, aquí hay una clase de persona que implementa Comparable:

public class Person implements Comparable<Person> {         
    private String name;  
    private int age;

    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }         

    @Override
    public int compareTo(Person o) {
        return this.getAge() - o.getAge();
    }
    @Override
    public String toString() {
        return this.getAge()+"-"+this.getName();
    }

}

Aquí es cómo usaría la clase anterior para ordenar una Lista en el orden natural de sus elementos, definido por la anulación del método compareTo() :

//-- usage
List<Person> pList = new ArrayList<Person>();
            Person p = new Person();
            p.setName("A");
            p.setAge(10);
            pList.add(p);
            p = new Person();
            p.setName("Z");
            p.setAge(20);
            pList.add(p);
            p = new Person();
            p.setName("D");
            p.setAge(30);
            pList.add(p);
            
            //-- natural sorting i.e comes with object implementation, by age
            Collections.sort(pList);

            System.out.println(pList);

Aquí es cómo usaría un Comparador en línea anónimo para ordenar una Lista que no implementa Comparable, o en este caso, para ordenar una Lista en un orden diferente al orden natural:

            //-- explicit sorting, define sort on another property here goes with name
            Collections.sort(pList, new Comparator<Person>() {

                @Override
                public int compare(Person o1, Person o2) {
                    return o1.getName().compareTo(o2.getName());
                }
            });            
            System.out.println(pList);

Ordenar entradas de mapa

A partir de Java 8, hay métodos predeterminados en la interfaz Map.Entry para permitir la clasificación de iteraciones de mapas.

Java SE 8
Map<String, Integer> numberOfEmployees = new HashMap<>();
numberOfEmployees.put("executives", 10);
numberOfEmployees.put("human ressources", 32);
numberOfEmployees.put("accounting", 12);
numberOfEmployees.put("IT", 100);

// Output the smallest departement in terms of number of employees
numberOfEmployees.entrySet().stream()
    .sorted(Map.Entry.comparingByValue())
    .limit(1)
    .forEach(System.out::println);   // outputs : executives=10

Por supuesto, estos también se pueden utilizar fuera de la api de flujo:

Java SE 8
List<Map.Entry<String, Integer>> entries = new ArrayList<>(numberOfEmployees.entrySet());
Collections.sort(entries, Map.Entry.comparingByValue());

Creando un comparador usando el método de comparación

Comparator.comparing(Person::getName)

Esto crea un comparador para la clase Person que usa este nombre de persona como fuente de comparación. También es posible usar la versión del método para comparar long, int y double. Por ejemplo:

Comparator.comparingInt(Person::getAge)

Orden invertida

Para crear un comparador que imponga el orden inverso, use el método reversed() :

Comparator.comparing(Person::getName).reversed()

Cadena de comparadores

Comparator.comparing(Person::getLastName).thenComparing(Person::getFirstName)

Esto creará un comparador que se compara con el apellido y luego con el nombre. Puedes encadenar tantos comparadores como quieras.



Modified text is an extract of the original Stack Overflow Documentation
Licenciado bajo CC BY-SA 3.0
No afiliado a Stack Overflow