Сортировка списков Java: примеры сопоставимых и компараторов

Научитесь сортировать список объектов по значению поля в Java, используя интерфейс Comparable(порядок сортировки по умолчанию) и интерфейс Comparator(дополнительные пользовательские порядки сортировки).

 Список список = ...;Comparator comparator = Comparator.reverseOrder(); //Создайте пользовательский порядок по мере необходимости//1 - Список.сорт()список.сортировка(null);список.сортировка(компаратор);//2 - Коллекции.сорт()Коллекции.сорт(список);Коллекции.сортировка(список, компаратор);//3 - Поток.sorted()Список sortedList = list.stream().sorted().toList(); //илиСписок reverseList = list.stream().sorted(компаратор).toList();

Обратите внимание, что если у нас есть миллионы записей для сортировки за раз, то запрос к базе данных — лучший способ. В противном случае использование интерфейса Comparable или Comparator — очень удобный подход.

1. Настройка

В примерах, приведенных в этом руководстве, мы будем использовать тип записи User. Он имеет четыре поля: id, firstName, lastName и age. Я выбрал эти поля намеренно, чтобы показать различные варианты использования.

 импорт java.io.Serializable;Пользователь публичной записи(длинный идентификатор, строка firstName, строка lastName, целое число age)реализует сериализуемый {публичный пользователь {если(возраст < 18) {throw new IllegalArgumentException("Вы не можете нанять несовершеннолетнего");}}}

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

private static List<User> getUnsortedUsers() {return Arrays.asList(new User(1L, "A", "Q", Integer.valueOf(24)),new User(4L, "B", "P", Integer.valueOf(22)),new User(2L, "C", "O", Integer.valueOf(27)),new User(3L, "D", "N", Integer.valueOf(29)),new User(5L, "E", "M", Integer.valueOf(25)));}

Далее мы будем использовать интерфейсы Comparable и Comparator для сортировки по различным значениям полей.

2. Сортировка списка с помощью Comparable для естественного упорядочивания

2.1. Реализация сопоставимого интерфейса

Интерфейс Comparable предоставляет один метод compareTo(T o) для реализации любым классом, чтобы можно было сравнить два объекта этого класса. Этот метод используется для реализации естественного поведения сортировки.

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

public record User(Long id, String firstName, String lastName, Integer age)implements Serializable, Comparable<User> {public User {if(age < 18) {throw new IllegalArgumentException("You cannot hire a minor person");}}@Overridepublic int compareTo(User o) {return this.id.intValue() - o.id.intValue();}}

2.2 Метод List.sort()

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

list.sort(Comparator.naturalOrder());

Если указанный компаратор равен нулю, то все элементы в списке должны реализовывать интерфейс Comparable, и будет использоваться естественный порядок элементов.

2.3 Метод Collections.sort()

Мы можем передать список объектов в метод sort(), который отсортирует объекты в их естественном порядке, т. е. по полю id.

Collections.sort( list );

Проверьте вывод в консоли.

 [Пользователь[id=1, имя=A, фамилия=Q, возраст=24],Пользователь[id=2, имя=C, фамилия=O, возраст=27],Пользователь[id=3, имя=D, фамилия=N, возраст=29],Пользователь[id=4, имя=B, фамилия=P, возраст=22],Пользователь[id=5, имя=E, фамилия=M, возраст=25]]

2.4 Метод Stream.sorted()

Java Stream API имеет метод sorted(), который может сортировать поток элементов в естественном порядке. Обратите внимание, что потоковые операции не изменяют исходные коллекции, поэтому объекты в списке останутся неизменными.

List<User> sortedList = list.stream().sorted().collect(Collectors.toList());

3. Сортировка списка с помощью компаратора для индивидуального упорядочивания

3.1 Создание экземпляров компаратора

Предположим, что мы хотим отсортировать список пользователей на основе некоторых других полей, например, по firstName или age. Мы можем изменить запись User, поскольку она уже реализует естественный порядок по полю id.

Здесь на помощь приходит интерфейс Comparator. Comparator можно использовать для определения пользовательского порядка. Для сортировки по разным полям объекта мы можем создать несколько реализаций Comparator.

Например, чтобы отсортировать список пользователей по firstName, мы можем создать класс FirstNameSorter, реализующий Comparator.

 импорт java.util.Comparator;открытый класс FirstNameSorter реализует Comparator<User> {@Переопределитьpublic int compare(Пользователь o1, Пользователь o2) {вернуть o1.firstName().compareTo(o2.firstName());}}

Обратите внимание, что мы можем использовать лямбда-выражение для создания встроенных экземпляров Comparator для одноразового использования.

 Comparator<Пользователь> firstNameSorter =(o1, o2) -> o1.firstName().compareTo(o2.firstName());

Мы можем создать эффект группировки сортировкой, объединив несколько компараторов с помощью метода Comparator.thenComparing(). Например, мы можем создать сложный компаратор fullNameSorter для сортировки списка по имени, а затем по фамилии.

 Comparator<Пользователь> firstNameSorter =(o1, o2) -> o1.firstName().compareTo(o2.firstName());Comparator<Пользователь> lastNameSorter =(o1, o2) -> o1.lastName().compareTo(o2.lastName());Comparator<Пользователь> fullNameSorter = firstNameSorter.thenComparing(lastNameSorter);

Стоит обсудить еще один тип Comparator, который используется для обратного упорядочивания. Мы можем получить этот обратный компаратор, вызвав метод reversed() на любом экземпляре компаратора.

 Comparator<Пользователь> reverseSorter = firstNameSorter.reversed();

Аналогичным образом мы можем создать столько компараторов, сколько необходимо в приложениях.

3.2.Коллекции.sort()

Для сортировки с помощью метода Collection.sort() передайте два аргумента метода. Первый аргумент — это несортированный список, а второй аргумент — экземпляр Comparator.

List<User> list = getUnsortedUsers();Comparator<User> firstNameSorter=(o1, o2) -> o1.firstName().compareTo(o2.firstName());Collections.sort(list, firstNameSorter);

3.3.Stream.sorted()

Чтобы отсортировать элементы потока с помощью экземпляра компаратора, мы можем передать компаратор в качестве аргумента метода sorted().

List<User> list = getUnsortedUsers();Comparator<User> firstNameSorter=(o1, o2) -> o1.firstName().compareTo(o2.firstName());List<User> sortedList = list.stream().sorted(firstNameSorter).collect(Collectors.toList());

4. Соблюдайте контракты hashCode() и equals()

Если мы переопределили метод equals() в классе User, всегда помните о необходимости соблюдать контракт между методами hashCode() и equals().

Если два объекта равны при использовании метода equals(), то метод compareTo() должен вернуть ноль.

Как правило, всегда используйте одни и те же поля в обоих методах. Если мы используем поле id в методе equals(), то также используйте поле id в методе compareTo(). Пример реализации приведен ниже:

import java.io.Serializable;import java.util.Objects;public record User(Long id, String firstName, String lastName, Integer age)implements Serializable, Comparable<User> {public User {if(age < 18) {throw new IllegalArgumentException("You cannot hire a minor person");}}@Overridepublic int compareTo(User o) {return this.id.intValue() - o.id.intValue();}@Overridepublic int hashCode() {return Objects.hash(id);}@Overridepublic boolean equals(Object obj) {if(this == obj)return true;if(obj == null)return false;if(getClass() != obj.getClass())return false;User other =(User) obj;return Objects.equals(id, other.id);}}

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

В этом руководстве Java Comparable и Comparator мы научились реализовывать оба интерфейса разными способами для разных вариантов использования. Мы также увидели использование обоих интерфейсов в Java Stream API.

Наконец, мы поняли, как правильно переопределять методы hashCode() и equals() для объектов, чтобы сортировка работала правильно.

Прокрутить вверх