Java Language
Vergelijkbaar en vergelijker
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...
}
});
}
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)
encompare(a,b)
een negatief geheel getal moeten retourneren, enb.compareTo(a)
encompare(b,a)
een positief geheel getal moet retourneren - Als
a > b
,a.compareTo(b)
encompare(a,b)
een positief geheel getal moeten retourneren, enb.compareTo(a)
encompare(b,a)
een negatief geheel getal moeten retourneren - Als
a
gelijk is aanb
voor vergelijking, moeten alle vergelijkingen0
opleveren.
Natuurlijk (vergelijkbaar) versus expliciet (comparator) sorteren
Er zijn twee methoden Collections.sort()
:
- Een die een
List<T>
als parameter waarbijT
Comparable moet implementeren en de methodecompareTo()
moet overschrijven die decompareTo()
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.
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:
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.