Jämförare och jämförbar i Java

1. Introduktion

Jämförelser i Java är ganska enkla - tills de inte är det.

När vi arbetar med anpassade typer eller försöker jämföra objekt som inte är direkt jämförbara måste vi använda en jämförelsestrategi. Vi kan enkelt bygga en, men med hjälp av Comparator eller Comparable- gränssnitt.

2. Ställa in exemplet

Låt oss ta ett exempel på ett fotbollslag - där vi vill ställa upp spelarna efter deras ranking.

Vi börjar med att skapa en enkel spelarklass :

public class Player { private int ranking; private String name; private int age; // constructor, getters, setters }

Låt oss sedan skapa en PlayerSorter- klass för att skapa vår samling och försöka sortera den med hjälp av Collections.sort :

public static void main(String[] args) { List footballTeam = new ArrayList(); Player player1 = new Player(59, "John", 20); Player player2 = new Player(67, "Roger", 22); Player player3 = new Player(45, "Steven", 24); footballTeam.add(player1); footballTeam.add(player2); footballTeam.add(player3); System.out.println("Before Sorting : " + footballTeam); Collections.sort(footballTeam); System.out.println("After Sorting : " + footballTeam); } 

Här, som förväntat, resulterar detta i ett kompileringsfel:

The method sort(List) in the type Collections is not applicable for the arguments (ArrayList)

Låt oss förstå vad vi gjorde fel här.

3. Jämförbar

Som namnet antyder är Comparable ett gränssnitt som definierar en strategi för att jämföra ett objekt med andra objekt av samma typ. Detta kallas klassens ”naturliga ordning”.

För att kunna sortera måste vi därför definiera vårt spelarobjekt som jämförbart genom att implementera det jämförbara gränssnittet:

public class Player implements Comparable { // same as before @Override public int compareTo(Player otherPlayer) { return Integer.compare(getRanking(), otherPlayer.getRanking()); } } 

Sorteringsordningen bestäms av returvärdet för jämförelsenTo () -metoden. Den Integer.compare (x, y) returnerar -1 om x är mindre än y , returer 0 om de är lika, och återgår ett annat.

Metoden returnerar ett tal som anger om objektet som jämförs är mindre än, lika med eller större än objektet som skickas som ett argument.

Slutligen, när vi kör vår PlayerSorter nu kan vi se våra spelare sorterade efter deras ranking:

Before Sorting : [John, Roger, Steven] After Sorting : [Steven, John, Roger]

Nu när vi har en klar förståelse för naturlig beställning med jämförbar , låt oss se hur vi kan använda andra typer av beställningar på ett mer flexibelt sätt än att direkt implementera ett gränssnitt.

4. Jämförelse

Den Komparatorn gränssnittet definierar en jämför (arg1, arg2) metod med två argument som representerar jämfört objekt och fungerar på liknande sätt som Comparable.compareTo () metoden.

4.1. Skapa komparatorer

För att skapa en komparator måste vi implementera komparatorgränssnittet .

I vårt första exempel skapar vi en komparator som använder spelarens rankningsattribut för att sortera spelarna:

public class PlayerRankingComparator implements Comparator { @Override public int compare(Player firstPlayer, Player secondPlayer) { return Integer.compare(firstPlayer.getRanking(), secondPlayer.getRanking()); } }

På samma sätt kan vi skapa en Comparator att använda ålders attribut Player att sortera spelarna:

public class PlayerAgeComparator implements Comparator { @Override public int compare(Player firstPlayer, Player secondPlayer) { return Integer.compare(firstPlayer.getAge(), secondPlayer.getAge()); } }

4.2. Jämförelser i aktion

För att demonstrera konceptet, låt oss modifiera vår PlayerSorter genom att införa ett andra argument till Collections.sort-metoden som faktiskt är den instans av Comparator vi vill använda.

Med denna metod kan vi åsidosätta den naturliga ordningen :

PlayerRankingComparator playerComparator = new PlayerRankingComparator(); Collections.sort(footballTeam, playerComparator); 

Nu, låt oss köra vår PlayerRankingSorter för att se resultatet:

Before Sorting : [John, Roger, Steven] After Sorting by ranking : [Steven, John, Roger]

Om vi ​​vill ha en annan sorteringsordning behöver vi bara ändra komparatorn vi använder:

PlayerAgeComparator playerComparator = new PlayerAgeComparator(); Collections.sort(footballTeam, playerComparator);

Nu när vi kör vår PlayerAgeSorter kan vi se en annan sorteringsordning efter ålder:

Before Sorting : [John, Roger, Steven] After Sorting by age : [Roger, John, Steven]

4.3. Java 8- komparatorer

Java 8 ger nya sätt att definiera komparatorer med hjälp av lambdauttryck och jämförande () statiska fabriksmetoden.

Låt oss se ett snabbt exempel på hur du använder ett lambdauttryck för att skapa en komparator :

Comparator byRanking = (Player player1, Player player2) -> Integer.compare(player1.getRanking(), player2.getRanking());

Den Comparator.comparing metoden tar en metod att beräkna den egendom som kommer att användas för att jämföra objekt och returnerar ett matchande Comparator instans:

Comparator byRanking = Comparator .comparing(Player::getRanking); Comparator byAge = Comparator .comparing(Player::getAge);

Du kan utforska Java 8-funktionerna på djupet i vår Java 8 Comparator.comparing guide.

5. Jämförelse jämfört med jämförbar

Det jämförbara gränssnittet är ett bra val när det används för att definiera standardbeställning eller, med andra ord, om det är det viktigaste sättet att jämföra objekt.

Sedan måste vi fråga oss varför använda en Comparator om vi redan har Comparable ?

Det finns flera anledningar till att:

  • Sometimes, we can't modify the source code of the class whose objects we want to sort, thus making the use of Comparable impossible
  • Using Comparators allows us to avoid adding additional code to our domain classes
  • We can define multiple different comparison strategies which isn't possible when using Comparable

6. Avoiding the Subtraction Trick

Over the course of this tutorial, we used the Integer.compare() method to compare two integers. One might argue that we should use this clever one-liner instead:

Comparator comparator = (p1, p2) -> p1.getRanking() - p2.getRanking();

Although it's much more concise compared to other solutions, it can be a victim of integer overflows in Java:

Player player1 = new Player(59, "John", Integer.MAX_VALUE); Player player2 = new Player(67, "Roger", -1); List players = Arrays.asList(player1, player2); players.sort(comparator);

Since -1 is much less than the Integer.MAX_VALUE, “Roger” should come before the “John” in the sorted collection. However, due to integer overflow, the “Integer.MAX_VALUE – (-1)” will be less than zero. So, based on the Comparator/Comparable contract, the Integer.MAX_VALUE is less than -1, which is obviously incorrect.

Hence, despite what we expected, “John” comes before the “Roger” in the sorted collection:

assertEquals("John", players.get(0).getName()); assertEquals("Roger", players.get(1).getName());

7. Conclusion

In this tutorial, we explored the Comparable and Comparator interfaces and discussed the differences between them.

To understand more advanced topics of sorting, check out our other articles such as Java 8 Comparator, Java 8 Comparison with Lambdas.

Och som vanligt kan källkoden hittas på GitHub.