Comparator comparable java пример

Компаратор и Comparable в Java

При работе с пользовательскими типами или при попытке сравнить объекты, которые нельзя сравнивать напрямую, нам необходимо использовать стратегию сравнения. Мы можем создать его, просто используя интерфейсы Comparator или Comparable .

2. Настройка примера

Давайте возьмем пример футбольной команды, где мы хотим выстроить игроков по их рейтингу.

Мы начнем с создания простого класса Player :

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

Далее мы создадим класс PlayerSorter для создания нашей коллекции и попытаемся отсортировать ее с помощью Collections.sort :

 public static void main(String[] args)    ListPlayer> 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);   > 

Как и ожидалось, это приводит к ошибке времени компиляции:

 The method sort(ListT>) in the type Collections   is not applicable for the arguments (ArrayListPlayer>) 

Теперь попробуем понять, что мы здесь сделали не так.

3. Сопоставимые

Как следует из названия, Comparable — это интерфейс, определяющий стратегию сравнения объекта с другими объектами того же типа. Это называется «естественным упорядочением» класса.

Чтобы иметь возможность сортировать, мы должны определить наш объект Player как сопоставимый, реализовав интерфейс Comparable :

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

Порядок сортировки определяется возвращаемым значением метода compareTo() . Integer.compare (x, y) возвращает -1, если x меньше y , 0, если они равны, и 1 в противном случае.

Метод возвращает число, указывающее, меньше, равно или больше сравниваемый объект, чем объект, передаваемый в качестве аргумента.

Теперь, когда мы запускаем наш PlayerSorter , мы можем видеть наших игроков , отсортированных по их рейтингу:

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

Теперь, когда у нас есть четкое представление о естественном упорядочении с помощью Comparable , давайте посмотрим, как мы можем использовать другие типы упорядочения более гибким образом, чем путем прямой реализации интерфейса.

4. Компаратор

Интерфейс Comparator определяет метод compare(arg1, arg2) с двумя аргументами, представляющими сравниваемые объекты, и работает аналогично методу Comparable.compareTo() .

4.1. Создание компараторов

Чтобы создать Comparator, мы должны реализовать интерфейс Comparator .

Для нашего первого примера мы создадим Comparator , чтобы использовать атрибут ранжирования Player для сортировки игроков:

 public class PlayerRankingComparator implements ComparatorPlayer>     @Override   public int compare(Player firstPlayer, Player secondPlayer)    return Integer.compare(firstPlayer.getRanking(), secondPlayer.getRanking());   >    > 

Точно так же мы можем создать Comparator , чтобы использовать возрастной атрибут Player для сортировки игроков:

 public class PlayerAgeComparator implements ComparatorPlayer>     @Override   public int compare(Player firstPlayer, Player secondPlayer)    return Integer.compare(firstPlayer.getAge(), secondPlayer.getAge());   >    > 

4.2. Компараторы в действии

Чтобы продемонстрировать концепцию, давайте изменим наш PlayerSorter , введя второй аргумент в метод Collections.sort , который на самом деле является экземпляром Comparator , который мы хотим использовать.

Используя этот подход, мы можем переопределить естественный порядок :

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

Теперь давайте запустим наш PlayerRankingSorter, чтобы увидеть результат:

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

Если нам нужен другой порядок сортировки, нам нужно только изменить используемый компаратор :

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

Теперь, когда мы запускаем наш PlayerAgeSorter , мы видим другой порядок сортировки по возрасту:

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

4.3. Компараторы Java 8 «

Java 8 предоставляет новые способы определения компараторов с помощью лямбда-выражений и статического фабричного метода сравнения() .

Давайте посмотрим на краткий пример того, как использовать лямбда-выражение для создания Comparator :

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

Метод Comparator.comparing принимает метод, вычисляющий свойство, которое будет использоваться для сравнения элементов, и возвращает соответствующий экземпляр Comparator :

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

Чтобы подробно изучить функциональные возможности Java 8, ознакомьтесь с нашим руководством по сравнению Java 8 Comparator.comparing .

5. Компаратор против сравнимого

Интерфейс Comparable — хороший выбор для определения порядка по умолчанию или, другими словами, если это основной способ сравнения объектов.

Так зачем использовать Comparator , если у нас уже есть Comparable ?

Есть несколько причин, почему:

  • Иногда мы не можем изменить исходный код класса, объекты которого мы хотим отсортировать, что делает невозможным использование Comparable .
  • Использование компараторов позволяет избежать добавления дополнительного кода в классы предметной области.
  • Мы можем определить несколько разных стратегий сравнения, что невозможно при использовании Comparable.

6. Как избежать трюка с вычитанием​

В ходе этого руководства мы использовали метод Integer.compare() для сравнения двух целых чисел. Тем не менее, кто-то может возразить, что вместо этого мы должны использовать этот умный однострочник:

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

Хотя это гораздо более лаконично, чем другие решения, оно может стать жертвой целочисленного переполнения в Java :

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

Поскольку -1 намного меньше, чем Integer.MAX_VALUE , «Роджер» должен стоять перед «Джоном» в отсортированной коллекции. Однако из-за целочисленного переполнения «Integer.MAX_VALUE — (-1)» будет меньше нуля . Таким образом, на основе контракта Comparator/ Comparable Integer.MAX_VALUE меньше -1, что явно неверно.

Поэтому, несмотря на то, что мы ожидали, «Джон» стоит перед «Роджером» в отсортированном наборе:

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

7. Заключение

В этой статье мы рассмотрели интерфейсы Comparable и Comparator и обсудили различия между ними.

Чтобы понять более сложные темы сортировки, ознакомьтесь с другими нашими статьями, такими как Компаратор Java 8 и Сравнение Java 8 с Lambdas .

Как обычно, исходный код можно найти на GitHub .

Источник

Comparator comparable java пример

В прошлой теме была рассмотрена работа коллекции TreeSet, типизированной объектами String. При добавлении новых элементов объект TreeSet автоматически проводит сортировку, помещая новый объект на правильное для него место. Однако со строками все понятно. А что если бы мы использовали не строки, а свои классы, например, следующий класс Person:

class Person < private String name; Person(String name)< this.name=name; >String getName() >

Объект TreeSet мы не сможем типизировать данным классом, поскольку в случае добавления объектов TreeSet не будет знать, как их сравнивать, и следующий кусок кода не будет работать:

TreeSet people = new TreeSet(); people.add(new Person("Tom"));

При выполнении этого кода мы столкнемся с ошибкой, которая скажет, что объект Person не может быть преобразован к типу java.lang.Comparable.

Для того, чтобы объекты Person можно было сравнить и сортировать, они должны применять интерфейс Comparable . При применении интерфейса он типизируется текущим классом. Применим его к классу Person:

class Person implements Comparable < private String name; Person(String name)< this.name = name; >String getName() public int compareTo(Person p) < return name.compareTo(p.getName()); >>

Интерфейс Comparable содержит один единственный метод int compareTo(E item) , который сравнивает текущий объект с объектом, переданным в качестве параметра. Если этот метод возвращает отрицательное число, то текущий объект будет располагаться перед тем, который передается через параметр. Если метод вернет положительное число, то, наоборот, после второго объекта. Если метод возвратит ноль, значит, оба объекта равны.

В данном случае мы не возвращаем явным образом никакое число, а полагаемся на встроенный механизм сравнения, который есть у класса String. Но мы также можем определить и свою логику, например, сравнивать по длине имени:

public int compareTo(Person p)

Теперь мы можем типизировать TreeSet типом Person и добавлять в дерево соответствующие объекты:

TreeSet people = new TreeSet(); people.add(new Person("Tom"));

Интерфейс Comparator

Однако перед нами может возникнуть проблема, что если разработчик не реализовал в своем классе, который мы хотим использовать, интерфейс Comparable, либо реализовал, но нас не устраивает его функциональность, и мы хотим ее переопределить? На этот случай есть еще более гибкий способ, предполагающий применение интерфейса Comparator.

Интерфейс Comparator содержит ряд методов, ключевым из которых является метод compare() :

public interface Comparator  < int compare(T a, T b); // остальные методы >

Метод compare также возвращает числовое значение — если оно отрицательное, то объект a предшествует объекту b, иначе — наоборот. А если метод возвращает ноль, то объекты равны. Для применения интерфейса нам вначале надо создать класс компаратора, который реализует этот интерфейс:

class PersonComparator implements Comparator < public int compare(Person a, Person b)< return a.getName().compareTo(b.getName()); >>

Здесь опять же проводим сравнение по строкам. Теперь используем класс компаратора для создания объекта TreeSet:

PersonComparator pcomp = new PersonComparator(); TreeSet people = new TreeSet(pcomp); people.add(new Person(«Tom»)); people.add(new Person(«Nick»)); people.add(new Person(«Alice»)); people.add(new Person(«Bill»)); for(Person p : people)

Для создания TreeSet здесь используется одна из версий конструктора, которая в качестве параметра принимает компаратор. Теперь вне зависимости от того, реализован ли в классе Person интерфейс Comparable, логика сравнения и сортировки будет использоваться та, которая определена в классе компаратора.

Сортировка по нескольким критериям

Начиная с JDK 8 в механизм работы компараторов были внесены некоторые дополнения. В частности, теперь мы можем применять сразу несколько компараторов по принципу приоритета. Например, изменим класс Person следующим образом:

class Person < private String name; private int age; public Person(String n, int a)< name=n; age=a; >String getName() int getAge() >

Здесь добавлено поле для хранения возраста пользователя. И, допустим, нам надо отсортировать пользователей по имени и по возрасту. Для этого определим два компаратора:

class PersonNameComparator implements Comparator < public int compare(Person a, Person b)< return a.getName().compareTo(b.getName()); >> class PersonAgeComparator implements Comparator < public int compare(Person a, Person b)< if(a.getAge()>b.getAge()) return 1; else if(a.getAge() < b.getAge()) return -1; else return 0; >>

Интерфейс компаратора определяет специальный метод по умолчанию thenComparing , который позволяет использовать цепочки компараторов для сортировки набора:

Comparator pcomp = new PersonNameComparator().thenComparing(new PersonAgeComparator()); TreeSet people = new TreeSet(pcomp); people.add(new Person(«Tom», 23)); people.add(new Person(«Nick»,34)); people.add(new Person(«Tom»,10)); people.add(new Person(«Bill»,14)); for(Person p : people)

Bill 14 Nick 34 Tom 10 Tom 23

В данном случае сначала применяется сортировка по имени, а потом по возрасту.

Источник

Читайте также:  Python isinstance int float
Оцените статью