Zoeken…


Syntaxis

  • openbare klasse MyClass implementeert vergelijkbare <MyClass >
  • openbare klasse MyComparator implementeert Comparator <SomeOtherClass >
  • public int CompareTo (MyClass other)
  • public int Compare (SomeOtherClass o1, SomeOtherClass o2)

Opmerkingen

Bij het implementeren van een compareTo(..) werkwijze die afhangt van een double , hebben de volgende niet:

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

De afkapping veroorzaakt door de (int) cast zorgt ervoor dat de methode soms onjuist 0 retourneert in plaats van een positief of negatief getal, en kan dus leiden tot vergelijkings- en sorteerfouten.

In plaats daarvan is het gebruik van Double.compare de eenvoudigste juiste implementatie:

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

Sinds Java 1.2 bestaat er een niet-generieke versie van Comparable<T> , simpelweg Comparable . Afgezien van de interface met legacy-code, is het altijd beter om de generieke versie Comparable<T> te implementeren, omdat het bij het vergelijken niet nodig is om te casten.


Het is heel standaard dat een klasse vergelijkbaar is met zichzelf, zoals in:

public class A implements Comparable<A>

Hoewel het mogelijk is om van dit paradigma te breken, wees voorzichtig wanneer je dit doet.


Een Comparator<T> kan nog steeds worden gebruikt voor instanties van een klasse als die klasse Comparable<T> implementeert. In dit geval zal de logica van de Comparator worden gebruikt; de natuurlijke volgorde zoals opgegeven door de Comparable implementatie wordt genegeerd.

Een lijst sorteren met Vergelijkbaar of een vergelijker

Stel dat we werken aan een klasse die een persoon bij zijn voor- en achternaam vertegenwoordigt. We hebben hiervoor een basisklasse gemaakt en de juiste equals en hashCode methoden hashCode .

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

Nu willen we een lijst met Person objecten sorteren op hun naam, zoals in het volgende 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.
}

Helaas, zoals aangegeven, zal het bovenstaande momenteel niet compileren. Collections.sort(..) weet alleen hoe een lijst te sorteren als de elementen in die lijst vergelijkbaar zijn of als een aangepaste vergelijkingsmethode wordt gegeven.

Als je wordt gevraagd om de volgende lijst te sorteren: 1,3,5,4,2 , zou je geen problemen hebben om te zeggen dat het antwoord 1,2,3,4,5 . Dit komt omdat gehele getallen (zowel in Java als wiskundig) een natuurlijke volgorde hebben , een standaard, standaard vergelijkingsbasis. Om onze Person-klasse een natuurlijke volgorde te geven, implementeren we Comparable<Person> , waarvoor de methode compareTo(Person p): moet worden geïmplementeerd 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);
        }
    }
}

Nu zal de belangrijkste methode correct werken

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
}

Als u de klasse Person echter niet wilt of niet kunt wijzigen, kunt u een aangepaste Comparator<T> opgeven die de vergelijking van twee Person objecten afhandelt. Als je wordt gevraagd om de volgende lijst te sorteren: circle, square, rectangle, triangle, hexagon zou dat niet kunnen, maar als je werd gevraagd om die lijst te sorteren op basis van het aantal hoeken , zou dat kunnen. Daarom geeft Java een vergelijker hoe twee normaal niet vergelijkbare objecten te vergelijken.

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
}

Vergelijkers kunnen ook worden gemaakt / gebruikt als een anonieme binnenklasse

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

Op lambda-expressie gebaseerde vergelijkers

Vanaf Java 8 kunnen vergelijkers ook worden uitgedrukt als lambda-uitdrukkingen

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

Comparator standaardmethoden

Verder zijn er interessante standaardmethoden op de Comparator-interface voor het bouwen van comparators: het volgende bouwt een comparator op lastName en vervolgens firstName .

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

De volgorde van een comparator omkeren

Elke comparator kan ook eenvoudig worden omgekeerd met behulp van de reversedMethod die de oplopende volgorde wijzigt in aflopend.

De vergelijkTo en vergelijk methoden

De Comparable<T> -interface vereist één methode:

public interface Comparable<T> {

    public int compareTo(T other);

}

En de Comparator<T> interface vereist een methode:

public interface Comparator<T> {

    public int compare(T t1, T t2);

}

Beide methoden wezen hetzelfde, met een klein verschil: compareTo vergelijkt this met other , terwijl compare vergelijking t1 tot t2 , niet zorgzaam over this .

Afgezien van dat verschil, hebben de twee methoden vergelijkbare eisen. Specifiek (voor vergelijkTo), vergelijkt dit object met het opgegeven object voor bestelling. Retourneert een negatief geheel getal, nul of een positief geheel getal omdat dit object kleiner is dan, gelijk aan of groter is dan het opgegeven object. Voor de vergelijking van a en b :

  • Als a < b , a.compareTo(b) en compare(a,b) een negatief geheel getal moeten retourneren, en b.compareTo(a) en compare(b,a) een positief geheel getal moet retourneren
  • Als a > b , a.compareTo(b) en compare(a,b) een positief geheel getal moeten retourneren, en b.compareTo(a) en compare(b,a) een negatief geheel getal moeten retourneren
  • Als a gelijk is aan b voor vergelijking, moeten alle vergelijkingen 0 opleveren.

Natuurlijk (vergelijkbaar) versus expliciet (comparator) sorteren

Er zijn twee methoden Collections.sort() :

  • Een die een List<T> als parameter waarbij T Comparable moet implementeren en de methode compareTo() moet overschrijven die de compareTo() bepaalt.
  • Een die een lijst en een vergelijker als argumenten gebruikt, waarbij de vergelijker de sorteervolgorde bepaalt.

Ten eerste is hier een Person-klasse die Comparable implementeert:

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

}

Hier is hoe je de bovenstaande klasse zou gebruiken om een lijst te sorteren in de natuurlijke volgorde van de elementen, gedefinieerd door de methode 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);

Hier is hoe u een anonieme inline-vergelijker zou gebruiken om een lijst te sorteren die geen vergelijkbare lijst implementeert, of in dit geval om een lijst in een andere volgorde dan de natuurlijke volgorde te sorteren:

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

Kaartitems sorteren

Vanaf Java 8 zijn er standaardmethoden op de Map.Entry interface om het sorteren van Map.Entry mogelijk te maken.

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

Natuurlijk kunnen deze ook buiten de stream-api worden gebruikt:

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

Een vergelijker maken met behulp van de vergelijkingsmethode

Comparator.comparing(Person::getName)

Hiermee maakt u een vergelijker voor de klasse Person die deze naam als vergelijkingsbron gebruikt. Het is ook mogelijk om de methodeversie te gebruiken om long, int en double te vergelijken. Bijvoorbeeld:

Comparator.comparingInt(Person::getAge)

Omgekeerde volgorde

Gebruik de methode reversed() om een comparator te maken die de omgekeerde volgorde oplegt:

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

Vergelijkingsketen

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

Hiermee maakt u een vergelijker die wordt vergeleken met achternaam en vervolgens met voornaam wordt vergeleken. U kunt zoveel comparators koppelen als u wilt.



Modified text is an extract of the original Stack Overflow Documentation
Licentie onder CC BY-SA 3.0
Niet aangesloten bij Stack Overflow