Ricerca…


Sintassi

  • La classe pubblica MyClass implementa <MyClass > comparabile
  • MyComparator di classe pubblica implementa il comparatore <SomeOtherClass >
  • public int compareTo (MyClass altro)
  • int pubblico (SomeOtherClass o1, SomeOtherClass o2)

Osservazioni

Quando si implementa un compareTo(..) che dipende da un double , non effettuare le seguenti operazioni:

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

Il troncamento causato dal cast (int) farà sì che il metodo restituisca a volte in modo errato 0 anziché un numero positivo o negativo e possa quindi portare a confronti e errori di ordinamento.

Invece, l'implementazione corretta più semplice è utilizzare Double.compare , in quanto tale:

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

Una versione non generica di Comparable<T> , semplicemente Comparable , esiste da Java 1.2 . A parte l'interfaccia con il codice legacy, è sempre meglio implementare la versione generica Comparable<T> , poiché non richiede il casting al confronto.


È molto normale che una classe sia paragonabile a se stessa, come in:

public class A implements Comparable<A>

Mentre è possibile uscire da questo paradigma, sii cauto quando lo fai.


Un Comparator<T> può ancora essere utilizzato su istanze di una classe se tale classe implementa Comparable<T> . In questo caso, verrà utilizzata la logica del Comparator ; l'ordine naturale specificato dall'implementazione Comparable sarà ignorato.

Ordinamento di una lista usando Paragonabile o un comparatore

Diciamo che stiamo lavorando su una classe che rappresenta una persona con il loro nome e cognome. Abbiamo creato una classe base per farlo e implementato i metodi equals e hashCode appropriati.

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

Ora vorremmo ordinare una lista di oggetti Person per nome, come nel seguente scenario:

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

Sfortunatamente, come indicato, quanto sopra non verrà compilato. Collections.sort(..) sa solo come ordinare un elenco se gli elementi in quell'elenco sono comparabili o se viene fornito un metodo di confronto personalizzato.

Se ti è stato chiesto di ordinare il seguente elenco: 1,3,5,4,2 , non avresti problemi a dire che la risposta è 1,2,3,4,5 . Questo perché gli Integer (sia in Java che matematicamente) hanno un ordinamento naturale , un ordinamento di base di confronto standard di default. Per dare alla nostra classe Person un ordinamento naturale, implementiamo Comparable<Person> , che richiede l'implementazione del metodo 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);
        }
    }
}

Ora, il metodo principale fornito funzionerà correttamente

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
}

Se, tuttavia, non si desidera o non è possibile modificare la classe Person , è possibile fornire un Comparator<T> personalizzato Comparator<T> che gestisce il confronto di due oggetti Person . Se ti è stato chiesto di ordinare il seguente elenco: circle, square, rectangle, triangle, hexagon non puoi, ma se ti venisse chiesto di ordinare quell'elenco in base al numero di angoli , potresti farlo. Proprio così, fornire un comparatore indica a Java come confrontare due oggetti normalmente non confrontabili.

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
}

I comparatori possono anche essere creati / usati come una classe interiore anonima

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

Comparatori basati sull'espressione Lambda

A partire da Java 8, i comparatori possono anche essere espressi come espressioni lambda

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

Metodi predefiniti del comparatore

Inoltre, esistono interessanti metodi predefiniti nell'interfaccia di Comparatore per la costruzione di comparatori: il seguente costruisce un comparatore confrontandolo con lastName e quindi firstName .

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

Inversione dell'ordine di un comparatore

Qualsiasi comparatore può anche essere facilmente invertito usando il reversedMethod che cambierà l'ordine crescente in discendente.

Il confronto e confrontare i metodi

L'interfaccia Comparable<T> richiede un metodo:

public interface Comparable<T> {

    public int compareTo(T other);

}

E l'interfaccia Comparator<T> richiede un metodo:

public interface Comparator<T> {

    public int compare(T t1, T t2);

}

Questi due metodi fanno essenzialmente la stessa cosa, con una differenza minore: compareTo confronta this con gli other , mentre compare confronta t1 con t2 , senza preoccuparsi affatto di this .

A parte questa differenza, i due metodi hanno requisiti simili. Specificamente (per compareTo), confronta questo oggetto con l'oggetto specificato per l'ordine. Restituisce un numero intero negativo, zero o un numero intero positivo poiché questo oggetto è minore, uguale o maggiore dell'oggetto specificato. Così, per il confronto di a e b :

  • Se a < b , a.compareTo(b) e compare(a,b) dovrebbe restituire un intero negativo, e b.compareTo(a) e compare(b,a) dovrebbe restituire un intero positivo
  • Se a > b , a.compareTo(b) e compare(a,b) devono restituire un intero positivo e b.compareTo(a) e compare(b,a) devono restituire un numero intero negativo
  • Se a uguale b per il confronto, tutti i confronti dovrebbero restituire 0 .

Ordinamento naturale (comparabile) vs esplicito (comparatore)

Esistono due metodi Collections.sort() :

  • Uno che accetta un List<T> come parametro in cui T deve implementare Comparable e sovrascrive il metodo compareTo() che determina l'ordinamento.
  • Uno che accetta un elenco e un comparatore come argomenti, in cui il comparatore determina l'ordinamento.

Innanzitutto, ecco una classe Person che 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();
    }

}

Ecco come compareTo() la classe precedente per ordinare una lista nell'ordinamento naturale dei suoi elementi, definito dal metodo di confronto 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);

Ecco come utilizzare un comparatore in linea anonimo per ordinare un elenco che non implementa Comparable o, in questo caso, per ordinare un elenco in un ordine diverso dall'ordinamento naturale:

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

Ordinamento delle voci della mappa

A partire da Java 8, ci sono metodi predefiniti sull'interfaccia Map.Entry per consentire l'ordinamento delle iterazioni della mappa.

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

Ovviamente, questi possono anche essere usati al di fuori della stream stream:

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

Creazione di un comparatore utilizzando il metodo di confronto

Comparator.comparing(Person::getName)

Questo crea un comparatore per la classe Person che usa questo nome di persona come origine di confronto. Inoltre è possibile utilizzare la versione del metodo per confrontare long, int e double. Per esempio:

Comparator.comparingInt(Person::getAge)

Ordine inverso

Per creare un comparatore che impone l'ordine inverso usa il metodo reversed() :

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

Catena di comparatori

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

Questo creerà un comparatore che raffigura il cognome con il cognome e poi lo confronta con il nome. Puoi concatenare tutti i comparatori che vuoi.



Modified text is an extract of the original Stack Overflow Documentation
Autorizzato sotto CC BY-SA 3.0
Non affiliato con Stack Overflow