Java Language Trier une liste en utilisant Comparable ou un comparateur


Exemple

Supposons que nous travaillons sur une classe représentant une personne par son nom et son prénom. Nous avons créé une classe de base pour ce faire et implémenté des méthodes equals et hashCode appropriées.

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);
    }
}

Maintenant, nous aimerions trier une liste d'objets Person par leur nom, comme dans le scénario suivant:

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.
}

Malheureusement, comme indiqué ci-dessus, ce qui précède ne sera pas compilé. Collections.sort(..) sait seulement trier une liste si les éléments de cette liste sont comparables ou si une méthode de comparaison personnalisée est fournie.

Si on vous demandait de trier la liste suivante: 1,3,5,4,2 , vous n'auriez aucun problème à dire que la réponse est 1,2,3,4,5 . Cela est dû au fait que les entiers (à la fois en Java et en mathématiques) ont un classement naturel , un classement de base de comparaison standard par défaut. Pour donner à notre classe Person un ordre naturel, nous implémentons Comparable<Person> , qui nécessite l'implémentation de la méthode 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);
        }
    }
}

Maintenant, la méthode principale donnée fonctionnera correctement

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
}

Si, toutefois, vous ne souhaitez pas ou ne pouvez pas modifier la classe Person , vous pouvez fournir un Comparator<T> personnalisé Comparator<T> qui gère la comparaison de deux objets Person . Si on vous demandait de trier la liste suivante: circle, square, rectangle, triangle, hexagon vous ne le pouviez pas, mais si vous deviez trier cette liste en fonction du nombre de coins , vous pourriez le faire. De même, fournir un comparateur indique à Java comment comparer deux objets normalement non 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
}

Les comparateurs peuvent également être créés / utilisés en tant que classe interne anonyme

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

Comparateurs basés sur l'expression lambda

A partir de Java 8, les comparateurs peuvent également être exprimés en expressions lambda

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

Méthodes par défaut du comparateur

De plus, il existe des méthodes par défaut intéressantes sur l’interface Comparator pour construire des comparateurs: les éléments suivants construisent un comparateur comparant lastName et firstName .

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

Inverser l'ordre d'un comparateur

Tout comparateur peut également être facilement inversé à l'aide de la méthode reversedMethod qui changera l'ordre croissant en ordre décroissant.