Сортиране с Comparable и Comparator в Java

Програмистите често трябва да сортират елементи от база данни в колекция, масив или карта. В Java можем да реализираме какъвто и да е алгоритъм за сортиране с всеки тип. Използвайки Comparableинтерфейса и compareTo()метода, можем да сортираме, използвайки азбучен ред, Stringдължина, обратен азбучен ред или числа. В Comparatorинтерфейса ни позволява да направим същото, но по по-гъвкав начин.

Каквото и да искаме да направим, просто трябва да знаем как да приложим правилната логика за сортиране за дадения интерфейс и тип.

Вземете изходния код

Вземете кода за този Java Challenger. Можете да провеждате свои собствени тестове, докато следвате примерите.

Сортиране на списък на Java с персонализиран обект

За нашия пример ще използваме същия POJO, който сме използвали за други Java Challengers досега. В този първи пример ние прилагаме сравнимия интерфейс в Simpsonкласа, като използваме Simpsonв общия тип:

 class Simpson implements Comparable { String name; Simpson(String name) { this.name = name; } @Override public int compareTo(Simpson simpson) { return this.name.compareTo(simpson.name); } } public class SimpsonSorting { public static void main(String... sortingWithList) { List simpsons = new ArrayList(); simpsons.add(new SimpsonCharacter("Homer ")); simpsons.add(new SimpsonCharacter("Marge ")); simpsons.add(new SimpsonCharacter("Bart ")); simpsons.add(new SimpsonCharacter("Lisa ")); Collections.sort(simpsons); simpsons.stream().map(s -> s.name).forEach(System.out::print); Collections.reverse(simpsons); simpsons.stream().forEach(System.out::print); } } 

Имайте предвид, че сме заменили метода compareTo () и сме предали друг Simpsonобект. Ние също сме заменили toString()метода, само за да улесним четенето на примера.

На toStringконцертите метод цялата информация, от обекта. Когато отпечатаме обекта, изходът ще бъде това, което е внедрено в toString().

Методът compareTo ()

В compareTo()метода сравнява даден обект или текущия модел с определен обект за определяне реда на обекти. Ето бърз поглед как compareTo()работи:

  Ако сравнението се върне

  Тогава ...

  >= 1

  this.name > simpson.name

  0

  this.name == simpson.name

  <= -1

  this.name < simpson.name

Можем да използваме само класове, които са сравними с sort()метода. Ако се опитаме да предадем, Simpsonкоето не се изпълнява Comparable, ще получим грешка при компилация.

В sort()метод използва полиморфизъм чрез преминаване всеки обект, който е Comparable. След това обектите ще бъдат сортирани според очакванията.

Резултатът от предишния код ще бъде:

 Bart Homer Lisa Marge 

Ако искахме да обърнем реда, бихме могли да заменим sort()a за reverse(); от:

 Collections.sort(simpsons); 

да се:

 Collections.reverse(simpsons); 

Разполагането на reverse()метода би променило предишния изход на:

 Marge Lisa Homer Bart 

Сортиране на Java масив

В Java можем да сортираме масив с какъвто и да е тип, стига той да изпълнява Comparableинтерфейса. Ето пример:

 public class ArraySorting { public static void main(String... moeTavern) { int[] moesPints = new int[] {9, 8, 7, 6, 1}; Arrays.sort(moesPints); Arrays.stream(moesPints).forEach(System.out::print); Simpson[] simpsons = new Simpson[]{new Simpson("Lisa"), new Simpson("Homer")}; Arrays.sort(simpsons); Arrays.stream(simpsons).forEach(System.out::println); } } 

При първото sort()извикване масивът се сортира по:

 1 6 7 8 9 

Във второто sort()извикване се сортира на:

 Homer Lisa 

Имайте предвид, че потребителските обекти трябва да се внедрят, Comparableза да бъдат сортирани, дори като масив.

Мога ли да сортирам обекти без сравними?

Ако обектът на Simpson не се изпълнява Comparable, ще бъде хвърлен ClassCastException. Ако стартирате това като тест, ще видите нещо като следния изход:

 Error:(16, 20) java: no suitable method found for sort(java.util.List) method java.util.Collections.sort(java.util.List) is not applicable (inference variable T has incompatible bounds equality constraints: com.javaworld.javachallengers.sortingcomparable.Simpson lower bounds: java.lang.Comparable) method java.util.Collections.sort(java.util.List,java.util.Comparator) is not applicable (cannot infer type-variable(s) T (actual and formal argument lists differ in length)) 

Този дневник може да е объркващ, но не се притеснявайте. Само имайте предвид, че ClassCastExceptionще бъде хвърлено за всеки сортиран обект, който не реализира Comparableинтерфейса.

Сортиране на карта с TreeMap

API на Java включва много класове за подпомагане на сортирането, включително TreeMap. В примера по-долу използваме TreeMapсортиране на ключове в Map.

 public class TreeMapExample { public static void main(String... barney) { Map simpsonsCharacters = new TreeMap(); simpsonsCharacters.put(new SimpsonCharacter("Moe"), "shotgun"); simpsonsCharacters.put(new SimpsonCharacter("Lenny"), "Carl"); simpsonsCharacters.put(new SimpsonCharacter("Homer"), "television"); simpsonsCharacters.put(new SimpsonCharacter("Barney"), "beer"); System.out.println(simpsonsCharacters); } } 

TreeMapизползва compareTo()метода, реализиран от Comparableинтерфейса. Всеки елемент в резултата Mapсе сортира по своя ключ. В този случай изходът ще бъде:

 Barney=beer, Homer=television, Lenny=Carl, Moe=shotgun 

Не забравяйте обаче: ако обектът не се изпълни Comparable, ClassCastExceptionще бъде хвърлен a .

Сортиране на набор с TreeSet

В SetИнтерфейсът е отговорен за съхраняване на уникални стойности, но когато ние използваме изпълнението TreeSet, вмъкнати елементи ще бъдат сортирани автоматично, тъй като ние ги добавите:

 public class TreeSetExample { public static void main(String... barney) { Set simpsonsCharacters = new TreeSet(); simpsonsCharacters.add(new SimpsonCharacter("Moe")); simpsonsCharacters.add(new SimpsonCharacter("Lenny")); simpsonsCharacters.add(new SimpsonCharacter("Homer")); simpsonsCharacters.add(new SimpsonCharacter("Barney")); System.out.println(simpsonsCharacters); } } 

Резултатът от този код е:

 Barney, Homer, Lenny, Moe 

Отново, ако използваме обект, който не е Comparable, ClassCastExceptionще бъде хвърлен a .

Сортиране с компаратор

Ами ако не искахме да използваме същия compareTo()метод от класа POJO? Можем ли да заменим Comparableметода, за да използваме различна логика? По-долу е даден пример:

 public class BadExampleOfComparable { public static void main(String... args) { List characters = new ArrayList(); SimpsonCharacter homer = new SimpsonCharacter("Homer") { @Override public int compareTo(SimpsonCharacter simpson) { return this.name.length() - (simpson.name.length()); } }; SimpsonCharacter moe = new SimpsonCharacter("Moe") { @Override public int compareTo(SimpsonCharacter simpson) { return this.name.length() - (simpson.name.length()); } }; characters.add(homer); characters.add(moe); Collections.sort(characters); System.out.println(characters); } } 

As you can see, this code is complicated and includes a lot of repetition. We had to override the compareTo() method twice for the same logic. If there were more elements we would have to replicate the logic for each object.

Fortunately, we have the Comparator interface, which lets us detach the compareTo() logic from Java classes. Consider the same example above rewritten using Comparator:

 public class GoodExampleOfComparator { public static void main(String... args) { List characters = new ArrayList(); SimpsonCharacter homer = new SimpsonCharacter("Homer"); SimpsonCharacter moe = new SimpsonCharacter("Moe"); characters.add(homer); characters.add(moe); Collections.sort(characters, (Comparator. comparingInt(character1 -> character1.name.length()) .thenComparingInt(character2 -> character2.name.length()))); System.out.println(characters); } } 

These examples demonstrate the main difference between Comparable and Comparator.

Use Comparable when there is a single, default comparison for your object. Use Comparatorwhen you need to work around an existing compareTo(), or when you need to use specific logic in a more flexible way. Comparator detaches the sorting logic from your object and contains the compareTo() logic within your sort() method.

Using Comparator with an anonymous inner class

In this next example, we use an anonymous inner class to compare the value of objects. An anonymous inner class, in this case, is any class that implements Comparator. Using it means we are not bound to instantiating a named class implementing an interface; instead, we implement the compareTo() method inside the anonymous inner class.

 public class MarvelComparator { public static void main(String... comparator) { List marvelHeroes = new ArrayList(); marvelHeroes.add("SpiderMan "); marvelHeroes.add("Wolverine "); marvelHeroes.add("Xavier "); marvelHeroes.add("Cyclops "); Collections.sort(marvelHeroes, new Comparator() { @Override public int compare(String hero1, String hero2) { return hero1.compareTo(hero2); } }); Collections.sort(marvelHeroes, (m1, m2) -> m1.compareTo(m2)); Collections.sort(marvelHeroes, Comparator.naturalOrder()); marvelHeroes.forEach(System.out::print); } } 

More about inner classes

An anonymous inner class is simply any class whose name doesn’t matter, and which implements the interface we are declaring. So in the example, the new Comparator is actually the instantiation of a class that doesn’t have a name, which implements the method with the logic we want.

Using Comparator with lambda expressions

Anonymous inner classes are verbose, which can cause problems in our code. In the Comparator interface, we can use lambda expressions to simplify and make the code easier to read. For example, we could change this:

 Collections.sort(marvel, new Comparator() { @Override public int compare(String hero1, String hero2) { return hero1.compareTo(hero2); } }); 

to this:

 Collections.sort(marvel, (m1, m2) -> m1.compareTo(m2)); 

Less code and the same result!

The output of this code would be:

 Cyclops SpiderMan Wolverine Xavier 

We could make the code even simpler by changing this:

 Collections.sort(marvel, (m1, m2) -> m1.compareTo(m2)); 

to this:

 Collections.sort(marvel, Comparator.naturalOrder()); 

Lambda expressions in Java

Learn more about lambda expressions and other functional programming techniques in Java.

Are the core Java classes Comparable?

Many core Java classes and objects implement the Comparable interface, which means we don’t have to implement the compareTo() logic for those classes. Here are a few familiar examples:

String

 public final class String implements java.io.Serializable, Comparable, CharSequence { ... 

Integer

 public final class Integer extends Number implements Comparable { … 

Double

 public final class Double extends Number implements Comparable {... 

There are many others. I encourage you to explore the Java core classes to learn their important patterns and concepts.

Приемете предизвикателството за сравним интерфейс!

Тествайте наученото, като разберете изхода на следния код. Не забравяйте, че ще научите най-добре, ако решите това предизвикателство за себе си само като го изучите. След като стигнете до отговор, можете да проверите отговора по-долу. Можете също така да изпълните свои собствени тестове, за да усвоите напълно концепциите.

 public class SortComparableChallenge { public static void main(String... doYourBest) { Set set = new TreeSet(); set.add(new Simpson("Homer")); set.add(new Simpson("Marge")); set.add(new Simpson("Lisa")); set.add(new Simpson("Bart")); set.add(new Simpson("Maggie")); List list = new ArrayList(); list.addAll(set); Collections.reverse(list); list.forEach(System.out::println); } static class Simpson implements Comparable { String name; public Simpson(String name) { this.name = name; } public int compareTo(Simpson simpson) { return simpson.name.compareTo(this.name); } public String toString() { return this.name; } } } 

Кой е резултатът от този код?

 A) Bart Homer Lisa Maggie Marge B) Maggie Bart Lisa Marge Homer C) Marge Maggie Lisa Homer Bart D) Indeterminate