Java Language
Vergleichbar und Vergleicher
Suche…
Syntax
- public class MyClass implementiert Comparable
<MyClass
> - public class MyComparator implementiert Comparator
<SomeOtherClass
> - public int compareTo (Meine andere)
- public int compare (SomeOtherClass o1, SomeOtherClass o2)
Bemerkungen
Wenn Sie eine compareTo(..)
-Methode implementieren, die von einem double
abhängig ist, gehen Sie nicht wie folgt vor:
public int comareTo(MyClass other) {
return (int)(doubleField - other.doubleField); //THIS IS BAD
}
Die durch die (int)
-Umgabe verursachte (int)
führt dazu, dass die Methode manchmal falsch anstatt einer positiven oder negativen Zahl 0
zurückgibt, was zu Vergleichen und Sortieren von Fehlern führen kann.
Stattdessen ist die einfachste korrekte Implementierung die Verwendung von Double.compare als solchen:
public int comareTo(MyClass other) {
return Double.compare(doubleField,other.doubleField); //THIS IS GOOD
}
Eine nicht generische Version von Comparable<T>
, einfach Comparable
, existiert seit Java 1.2 . Abgesehen von der Anbindung an älteren Code ist es immer besser, die generische Version Comparable<T>
zu implementieren, da kein Casting beim Vergleich erforderlich ist.
Es ist sehr Standard, dass eine Klasse mit sich selbst vergleichbar ist, wie in:
public class A implements Comparable<A>
Es ist zwar möglich, von diesem Paradigma abzubrechen, seien Sie jedoch vorsichtig.
Ein Comparator<T>
kann weiterhin für Instanzen einer Klasse verwendet werden, wenn diese Klasse Comparable<T>
implementiert. In diesem Fall wird die Logik des Comparator
verwendet. Die von der Comparable
Implementierung angegebene natürliche Reihenfolge wird ignoriert.
Sortieren einer Liste mit Comparable oder ein Komparator
Angenommen, wir arbeiten an einer Klasse, die eine Person mit ihrem Vor- und Nachnamen darstellt. Wir haben dafür eine grundlegende Klasse erstellt und richtige equals
und hashCode
Methoden implementiert.
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);
}
}
Nun möchten wir eine Liste von Person
nach ihrem Namen sortieren, wie im folgenden Szenario:
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.
}
Leider werden die oben genannten Punkte derzeit nicht kompiliert. Collections.sort(..)
eine Liste nur sortieren, wenn die Elemente in dieser Liste vergleichbar sind oder eine benutzerdefinierte Vergleichsmethode angegeben wird.
Wenn Sie gebeten werden, die folgende Liste zu sortieren: 1,3,5,4,2
, haben Sie kein Problem mit der Antwort 1,2,3,4,5
. Dies liegt daran, dass Integer (sowohl in Java als auch mathematisch) eine natürliche Reihenfolge haben , eine Standard-Standardvergleichsbasisordnung. Um unserer Person-Klasse eine natürliche Reihenfolge zu geben, implementieren wir compareTo(Person p):
Comparable<Person>
, was die Implementierung der Methode compareTo(Person p):
erfordert 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);
}
}
}
Die angegebene Hauptmethode funktioniert nun korrekt
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
}
Wenn Sie jedoch die Klasse Person
nicht ändern möchten oder können, können Sie einen benutzerdefinierten Comparator<T>
bereitstellen, der den Vergleich von zwei Person
übernimmt. Wenn Sie aufgefordert wurden, die folgende Liste zu sortieren: circle, square, rectangle, triangle, hexagon
, konnten Sie nicht. Wenn Sie jedoch aufgefordert wurden, diese Liste anhand der Anzahl der Ecken zu sortieren, können Sie dies tun. Wenn Sie einen Komparator bereitstellen, wird Java angewiesen, zwei normalerweise nicht vergleichbare Objekte zu vergleichen.
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
}
Komparatoren können auch als anonyme innere Klasse erstellt / verwendet werden
//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...
}
});
}
Vergleicher auf Lambda-Basis
Ab Java 8 können Komparatoren auch als Lambda-Ausdrücke ausgedrückt werden
//Lambda
Collections.sort(people, (p1, p2) -> { //Legal
//Method code....
});
Comparator-Standardmethoden
Darüber hinaus gibt es auf der Comparator-Benutzeroberfläche interessante Standardmethoden zum lastName
von Komparatoren: Im Folgenden wird ein Komparator erstellt, der nach lastName
und dann nach firstName
.
Collections.sort(people, Comparator.comparing(Person::getLastName)
.thenComparing(Person::getFirstName));
Umkehren der Reihenfolge eines Komparators
Jeder Komparator kann auch leicht mit der reversedMethod
wodurch die aufsteigende Reihenfolge in absteigend geändert wird.
Die compareTo und Compare-Methoden
Die Comparable<T>
-Schnittstelle erfordert eine Methode:
public interface Comparable<T> {
public int compareTo(T other);
}
Und die Comparator<T>
-Schnittstelle erfordert eine Methode:
public interface Comparator<T> {
public int compare(T t1, T t2);
}
Diese beiden Methoden tun im Wesentlichen die gleiche Sache, mit einem kleinen Unterschied: compareTo
vergleicht this
auf other
, während compare
vergleicht t1
bis t2
, nicht darum kümmern überhaupt über this
.
Abgesehen von diesem Unterschied haben die beiden Methoden ähnliche Anforderungen. Spezifisch (für compareTo) Vergleicht dieses Objekt mit dem angegebenen Objekt für die Bestellung. Gibt eine negative Ganzzahl, Null oder eine positive Ganzzahl zurück, da dieses Objekt kleiner als, gleich oder größer als das angegebene Objekt ist. Für den Vergleich von a
und b
:
- Wenn
a < b
,a.compareTo(b)
undcompare(a,b)
eine negative Ganzzahl undb.compareTo(a)
undcompare(b,a)
eine positive Ganzzahl zurückgeben sollen - Wenn
a > b
, solltena.compareTo(b)
undcompare(a,b)
eine positive Ganzzahl undb.compareTo(a)
undcompare(b,a)
eine negative Ganzzahl zurückgeben - Wenn
a
gleichb
zum Vergleich, sollten alle Vergleiche zurückkehren0
.
Natürliche (vergleichbare) vs. explizite (Vergleicher) Sortierung
Es gibt zwei Collections.sort()
-Methoden:
- Eine, die eine
List<T>
als Parameter verwendet, in derT
compareTo()
implementieren muss, und diecompareTo()
-Methode überschreiben, die diecompareTo()
bestimmt. - Eine, die eine Liste und einen Vergleicher als Argument verwendet, wobei der Vergleicher die Sortierreihenfolge festlegt.
Hier ist zunächst eine Person-Klasse, die Comparable implementiert:
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();
}
}
So würden Sie die obige Klasse verwenden, um eine Liste in der natürlichen Reihenfolge ihrer Elemente zu sortieren, die durch die Methode compareTo()
Methode überschrieben wird:
//-- 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);
So würden Sie einen anonymen Inline Comparator verwenden, um eine Liste zu sortieren, die Comparable nicht implementiert, oder in diesem Fall eine Liste in einer anderen Reihenfolge als der natürlichen Reihenfolge zu sortieren:
//-- 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);
Karteneinträge sortieren
Ab Java 8 gibt es in der Map.Entry
Schnittstelle Standardmethoden, um das Sortieren von Map.Entry
zu ermöglichen.
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
Natürlich können diese auch außerhalb der Stream-API verwendet werden:
List<Map.Entry<String, Integer>> entries = new ArrayList<>(numberOfEmployees.entrySet());
Collections.sort(entries, Map.Entry.comparingByValue());
Erstellen eines Komparators mit der Vergleichsmethode
Comparator.comparing(Person::getName)
Dadurch wird ein Vergleicher für die Klasse Person
, die diesen Personennamen als Vergleichsquelle verwendet. Es ist auch möglich, die Methodenversion zu verwenden, um long, int und double zu vergleichen. Zum Beispiel:
Comparator.comparingInt(Person::getAge)
Umgekehrte Reihenfolge
Um einen Komparator zu erstellen, der die umgekehrte Reihenfolge erzwingt, verwenden Sie die reversed()
Methode:
Comparator.comparing(Person::getName).reversed()
Kette von Komparatoren
Comparator.comparing(Person::getLastName).thenComparing(Person::getFirstName)
Dadurch wird ein Vergleicher erstellt, der mit dem Nachnamen und dann mit dem Vornamen verglichen wird. Sie können beliebig viele Komparatoren verketten.