Java Language
Jämförbar och jämförande
Sök…
Syntax
- public class MyClass implementerar Jämförbar
<MyClass
> - offentlig klass MyComparator implementerar Comparator
<SomeOtherClass
> - public int jämförTo (MyClass övrigt)
- public int jämför (SomeOtherClass o1, SomeOtherClass o2)
Anmärkningar
När du implementerar en compareTo(..)
-metod som beror på en double
, gör inte följande:
public int comareTo(MyClass other) {
return (int)(doubleField - other.doubleField); //THIS IS BAD
}
Trunkeringen orsakad av (int)
cast kommer att göra att metoden ibland felaktigt returnerar 0
istället för ett positivt eller negativt tal, och kan således leda till jämförelse och sortering av buggar.
Istället är den enklaste korrekta implementeringen att använda Double.compare , som sådan:
public int comareTo(MyClass other) {
return Double.compare(doubleField,other.doubleField); //THIS IS GOOD
}
En icke-generisk version av Comparable<T>
, helt enkelt Comparable
, har funnits sedan Java 1.2 . Annat än för att gränssnitt med äldre kod, är det alltid bättre att implementera den generiska versionen Comparable<T>
, eftersom den inte kräver gjutning vid jämförelse.
Det är mycket standard för en klass att vara jämförbar med sig själv, som i:
public class A implements Comparable<A>
Det är möjligt att bryta sig från detta paradigm, men var försiktig när du gör det.
En Comparator<T>
kan fortfarande användas i instanser av en klass om den klassen implementerar Comparable<T>
. I detta fall kommer Comparator
logik att användas; den naturliga beställningen som specificeras av den Comparable
implementeringen ignoreras.
Sortera en lista med jämförbar eller en komparator
Säg att vi arbetar på en klass som representerar en person med sina för- och efternamn. Vi har skapat en grundläggande klass för att göra detta och implementerat korrekt equals
och hashCode
metoder.
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 vill vi sortera en lista med Person
efter deras namn, till exempel i följande 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.
}
Tyvärr, som markerat, kommer ovanstående för närvarande inte att kompilera. Collections.sort(..)
vet bara hur man sorterar en lista om elementen i listan är jämförbara eller om en anpassad jämförelsemetod ges.
Om du ombads att sortera följande lista: 1,3,5,4,2
, har du inga problem att säga att svaret är 1,2,3,4,5
. Detta beror på att heltal (både i Java och matematiskt) har en naturlig beställning , en standardbeställning av standardjämförelse. För att ge vår personklass en naturlig beställning implementerar vi Comparable<Person>
, som kräver implementering av metoden 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 kommer den angivna huvudmetoden att fungera 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
}
Om du emellertid inte vill eller inte kan ändra klass Person
kan du tillhandahålla en anpassad Comparator<T>
som hanterar jämförelsen mellan två Person
objekt. Om du ombads att sortera följande lista: circle, square, rectangle, triangle, hexagon
kunde du inte, men om du blev ombedd att sortera den listan baserat på antalet hörn kunde du göra det. Bara så, genom att ge en komparator instruerar Java hur man kan jämföra två normalt inte jämförbara objekt.
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
}
Jämförare kan också skapas / användas som en anonym inre klass
//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...
}
});
}
Lambda-uttrycksbaserade komparatorer
Från Java 8 kan komparatorer också uttryckas som lambda-uttryck
//Lambda
Collections.sort(people, (p1, p2) -> { //Legal
//Method code....
});
Comparator standardmetoder
Dessutom finns det intressanta standardmetoder i Comparator-gränssnittet för att bygga komparatorer: följande bygger en komparator som jämför med lastName
och sedan firstName
.
Collections.sort(people, Comparator.comparing(Person::getLastName)
.thenComparing(Person::getFirstName));
Invertera ordningen för en komparator
Vilken som helst komparator kan också enkelt vändas med reversedMethod
som kommer att ändra stigande ordning till fallande.
Jämför & För att jämföra metoder
Comparable<T>
gränssnitt kräver en metod:
public interface Comparable<T> {
public int compareTo(T other);
}
Och gränssnittet Comparator<T>
kräver en metod:
public interface Comparator<T> {
public int compare(T t1, T t2);
}
Dessa två metoder gör i huvudsak samma sak, med en mindre skillnad: jämför compareTo
jämföra this
med other
, medan compare
jämför t1
till t2
, och bryr sig inte alls om this
.
Bortsett från denna skillnad har de två metoderna liknande krav. Specifikt (för jämförTo) Jämför detta objekt med det angivna objektet för ordning. Returnerar ett negativt heltal, noll eller ett positivt heltal eftersom detta objekt är mindre än, lika med eller större än det angivna objektet. För jämförelse av a
och b
:
- Om
a < b
,a.compareTo(b)
ochcompare(a,b)
ska returnera ett negativt heltal, ochb.compareTo(a)
ochcompare(b,a)
ska returnera ett positivt heltal - Om
a > b
,a.compareTo(b)
ochcompare(a,b)
ska returnera ett positivt heltal, ochb.compareTo(a)
ochcompare(b,a)
ska returnera ett negativt heltal - Om
a
lika medb
för jämförelse bör alla jämförelser returnera0
.
Naturlig (jämförbar) vs uttrycklig (komparator) sortering
Det finns två Collections.sort()
metoder Collections.sort()
:
- En som tar en
List<T>
som en parameter därT
måste implementera Jämförbar och åsidosättacompareTo()
som bestämmer sorteringsordning. - En som tar en lista och en komparator som argument, där komparatorn bestämmer sorteringsordningen.
Först här är en personklass som implementerar Jämförbar:
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();
}
}
Så här använder du klassen ovan för att sortera en lista i den naturliga ordningen av dess element, definierad av compareTo()
åsidosättande:
//-- 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);
Så här använder du en anonym inline Comparator för att sortera en lista som inte implementerar Jämförbar, eller i detta fall, för att sortera en lista i en annan ordning än den naturliga beställningen:
//-- 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);
Sortera kartposter
Från Java 8 finns det standardmetoder på Map.Entry
gränssnittet för att tillåta sortering av kart-iterationer.
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
Naturligtvis kan dessa också användas utanför strömmen api:
List<Map.Entry<String, Integer>> entries = new ArrayList<>(numberOfEmployees.entrySet());
Collections.sort(entries, Map.Entry.comparingByValue());
Skapa en komparator med jämförande metod
Comparator.comparing(Person::getName)
Detta skapar en komparator för klassen Person
som använder detta personnamn som jämförelsekälla. Det är också möjligt att använda metodversion för att jämföra lång, int och dubbel. Till exempel:
Comparator.comparingInt(Person::getAge)
Omvänd ordning
För att skapa en komparator som påför omvänd ordning använder reversed()
metod:
Comparator.comparing(Person::getName).reversed()
Kedja av komparatorer
Comparator.comparing(Person::getLastName).thenComparing(Person::getFirstName)
Detta skapar en komparator som jämför jämförelse med efternamn och jämför sedan med förnamn. Du kan kedja så många komparatorer som du vill.