Реализовать интерфейс Comparator
Интерфейс Comparator можно увидеть повсюду в библиотеке JDK: от поиска до сортировки и обратных операций и так далее. В Java 8 он становится функциональным интерфейсом. Преимущество этого заключается в том, что мы можем использовать синтаксис потоковой передачи для реализации компаратора.
Давайте реализуем Comparator несколькими различными способами, чтобы увидеть ценность нового синтаксиса. Ваши пальцы будут вам благодарны. Отсутствие необходимости реализовывать анонимные внутренние классы позволяет сэкономить массу нажатий клавиш.
Сортировка с помощью компаратора
В следующем примере будут использоваться различные методы сравнения для сортировки группы людей. Давайте сначала создадим Person JavaBean.
Скопируйте код кода следующим образом:
общественный класс Person {
частное последнее имя строки;
частный окончательный возраст;
public Person(final String theName, Final int theAge) {
имя = Имя;
возраст = возраст;
}
общественная строка getName() { возвращаемое имя }
public int getAge() {возвращает возраст};
public int ageDifference (последнее лицо другое) {
возраст возврата - др.возраст;
}
публичная строка toString() {
return String.format("%s - %d", name, age);
}
}
Мы можем реализовать интерфейс Comparator через класс Person, но таким образом мы сможем использовать только один метод сравнения. Мы хотим иметь возможность сравнивать различные атрибуты, например имя, возраст или их комбинацию. Чтобы гибко выполнять сравнение, мы можем использовать Comparator для генерации соответствующего кода, когда нам нужно сравнить.
Давайте сначала создадим список людей, у каждого из которых разные имена и возраст.
Скопируйте код кода следующим образом:
окончательный список<Person> люди = Arrays.asList(
новый человек("Джон", 20),
новый человек("Сара", 21),
новый человек("Джейн", 21),
новый человек("Грег", 35));
Мы можем сортировать людей по возрастанию или убыванию по имени или возрасту. Общий метод заключается в использовании анонимных внутренних классов для реализации интерфейса Comparator. Если написать таким образом, то смысл будет иметь только наиболее релевантный код, а все остальное — просто формальность. Использование лямбда-выражений позволяет сосредоточиться на сути сравнения.
Давайте сначала отсортируем их по возрасту от самого младшего к самому старшему.
Теперь, когда у нас есть объект List, мы можем использовать его метод sort() для сортировки. Однако этот метод также имеет свои проблемы. Это void-метод, а это значит, что когда мы вызываем этот метод, список изменится. Чтобы сохранить исходный список, мы должны сначала сделать копию, а затем вызвать метод sort(). Это было слишком много усилий. Сейчас нам придется обратиться за помощью к классу Stream.
Мы можем получить объект Stream из списка, а затем вызвать его метод sorted(). Он возвращает отсортированную коллекцию, а не изменяет исходную коллекцию. Используя этот метод, вы можете легко настроить параметры Компаратора.
Скопируйте код кода следующим образом:
Список<Person> по возрастаниюAge =
люди.поток()
.sorted((человек1, человек2) -> человек1.ageDifference(человек2))
.collect(toList());
printPeople("Сортировка по возрастанию возраста: ", Возрастание);
Сначала мы преобразуем список в объект Stream с помощью методаstream(). Затем вызовите его метод sorted(). Этот метод принимает параметр Comparator. Поскольку Comparator — это функциональный интерфейс, мы можем передать лямбда-выражение. Наконец, мы вызываем метод сбора и сохраняем результаты в списке. Метод сбора — это редуктор, который может выводить объекты во время итерационного процесса в определенный формат или тип. Метод toList() — статический метод класса Collectors.
Абстрактный метод CompareTo() класса Comparator получает два параметра, которые являются объектами для сравнения, и возвращает результат типа int. Чтобы быть совместимым с этим, наше лямбда-выражение также получает два параметра, два объекта Person, типы которых автоматически определяются компилятором. Мы возвращаем тип int, указывающий, равны ли сравниваемые объекты.
Поскольку мы хотим выполнить сортировку по возрасту, мы сравним возраст двух объектов, а затем вернем результат сравнения. Если они одинакового размера, верните 0. В противном случае возвращается отрицательное число, если первый человек моложе, и положительное число, если первый человек старше.
Метод sorted() обходит каждый элемент целевой коллекции и вызывает указанный компаратор, чтобы определить порядок сортировки элементов. Метод выполнения метода sorted() чем-то похож на упомянутый ранее метод уменьшить(). Метод уменьшения() постепенно сводит список к результату. Метод sorted() сортирует по результатам сравнения.
После сортировки мы хотим распечатать результаты, поэтому вызываем метод printPeople(), давайте реализуем этот метод;
Скопируйте код кода следующим образом:
public static void printPeople(
окончательное сообщение String, окончательный список<Person> люди) {
System.out.println(сообщение);
люди.forEach(System.out::println);
}
В этом методе мы сначала печатаем сообщение, затем просматриваем список и распечатываем каждый его элемент.
Давайте вызовем метод sorted(), чтобы посмотреть, как он будет сортировать людей в списке от самых молодых к самым старым.
Скопируйте код кода следующим образом:
Сортировка в порядке возрастания возраста:
Джон - 20
Сара - 21
Джейн - 21
Грег – 35
Давайте еще раз взглянем на метод sorted(), чтобы внести улучшения.
Скопируйте код кода следующим образом:
.sorted((человек1, человек2) -> человек1.ageDifference(человек2))
В переданном лямбда-выражении мы просто маршрутизируем эти два параметра: первый параметр используется в качестве цели вызова метода ageDifference(), а второй параметр используется в качестве его параметра. Но мы можем не писать так, а использовать режим офисного пространства — то есть использовать ссылки на методы и позволить компилятору Java выполнять маршрутизацию.
Используемая здесь маршрутизация параметров немного отличается от того, что мы видели раньше. Ранее мы видели, что аргументы передаются либо как цели вызова, либо как параметры вызова. Теперь у нас есть два параметра, и мы хотим разделить их на две части: одну как цель вызова метода, а вторую как параметр. Не волнуйтесь, компилятор Java скажет вам: «Я позабочусь об этом».
Мы можем заменить лямбда-выражение в предыдущем методе sorted() коротким и лаконичным методом ageDifference.
Скопируйте код кода следующим образом:
люди.поток()
.sorted(Человек::ageDifference)
Этот код очень краток благодаря ссылкам на методы, предоставляемым компилятором Java. Компилятор получает два параметра экземпляра person и использует первый в качестве цели метода ageDifference(), а второй — в качестве параметра метода. Мы позволяем компилятору выполнять эту работу вместо того, чтобы писать код напрямую. При использовании этого метода мы должны убедиться, что первый параметр является вызывающей целью метода, на который ссылается, а оставшийся параметр — входным параметром метода.
Повторное использование компаратора
Людей в списке легко отсортировать от самых молодых к самым старым, а также легко отсортировать от самых старых к самым молодым. Давайте попробуем.
Скопируйте код кода следующим образом:
printPeople("Сортировка по убыванию возраста: ",
люди.поток()
.sorted((человек1, человек2) -> человек2.ageDifference(человек1))
.collect(toList()));
Мы вызываем метод sorted() и передаем лямбда-выражение, которое соответствует интерфейсу Comparator, как и в предыдущем примере. Единственная разница заключается в реализации этого лямбда-выражения — мы изменили порядок сравниваемых людей. Результаты должны быть упорядочены от старшего к младшему с учетом их возраста. Давайте посмотрим.
Скопируйте код кода следующим образом:
Сортировка по убыванию возраста:
Грег – 35
Сара - 21
Джейн - 21
Джон - 20
Не нужно много усилий, чтобы просто изменить логику сравнения. Но мы не можем реконструировать эту версию в ссылку на метод, поскольку порядок параметров не соответствует правилам маршрутизации параметров для ссылок на метод: первый параметр используется не как вызывающая цель метода, а как параметр метода; Есть способ решить эту проблему, который также уменьшает дублирование усилий. Давайте посмотрим, как это сделать.
Ранее мы создали два лямбда-выражения: одно для сортировки по возрасту от меньшего к большему, а другое — от большого к меньшему. Это приведет к избыточности и дублированию кода и нарушит принцип DRY. Если мы просто хотим настроить порядок сортировки, JDK предоставляет обратный метод, который по умолчанию имеет специальный модификатор метода. Мы обсудим это в методе по умолчанию на стр. 77. Здесь мы сначала используем метод Reverse() для удаления избыточности.
Скопируйте код кода следующим образом:
Comparator<Person> CompareAcending =
(человек1, человек2) -> человек1.ageDifference(человек2);
Comparator<Person> CompareDescending = CompareAscending.reversed();
Сначала мы создали компаратор CompareAscending, чтобы сортировать людей по возрасту от самого младшего к самому старшему. Чтобы изменить порядок сравнения, вместо того, чтобы писать этот код заново, нам нужно всего лишь вызвать метод Reverse() первого компаратора, чтобы получить второй объект компаратора. Под капотом метода Reversed() он создает компаратор, изменяющий порядок сравниваемых параметров. Это показывает, что reverse также является методом более высокого порядка — он создает и возвращает функцию без побочных эффектов. Мы используем эти два компаратора в коде.
Скопируйте код кода следующим образом:
printPeople("Сортировка по возрастанию: ",
люди.поток()
.sorted(сравнитьпо возрастанию)
.collect(toList())
);
printPeople("Сортировка по убыванию возраста: ",
люди.поток()
.sorted(сравнить по убыванию)
.collect(toList())
);
Из кода ясно видно, что эти новые функции Java8 значительно уменьшили избыточность и сложность кода, но преимущества гораздо больше, чем эти. В JDK вас ждут бесконечные возможности.
Мы уже можем сортировать по возрасту, также легко сортировать по имени. Давайте отсортируем их лексикографически по имени. Аналогично, нам нужно только изменить логику в лямбда-выражении.
Скопируйте код кода следующим образом:
printPeople("Отсортировано по возрастанию имени: ",
люди.поток()
.sorted((человек1, человек2) ->
person1.getName().compareTo(person2.getName()))
.collect(toList()));
Результаты вывода будут отсортированы в лексикографическом порядке по имени.
Скопируйте код кода следующим образом:
Отсортировано по возрастанию имени:
Грег – 35
Джейн - 21
Джон - 20
Сара - 21
До сих пор мы сортировали либо по возрасту, либо по имени. Мы можем сделать логику лямбда-выражений более разумной. Например, мы можем сортировать по возрасту и имени одновременно.
Давайте выберем самого молодого человека в списке. Мы можем сначала отсортировать по возрасту от самого маленького к самому большому, а затем выбрать в результатах первый. Но на самом деле это не работает. В Stream есть метод min() для достижения этой цели. Этот метод также принимает компаратор, но возвращает наименьший объект в коллекции. Давайте воспользуемся этим.
Скопируйте код кода следующим образом:
люди.поток()
.min(Человек::ageDifference)
.ifPresent(самый молодой -> System.out.println("Самый молодой: " + самый младший));
При вызове метода min() мы использовали ссылку на метод ageDifference. Метод min() возвращает объект Optinal, поскольку список может быть пустым и в нем может быть более одного самого молодого человека. Затем мы получаем самого младшего человека с помощью метода ifPrsend() Optinal и распечатываем его подробную информацию. Давайте посмотрим на результат.
Скопируйте код кода следующим образом:
Самый младший: Джон, 20 лет.
Экспортировать самый старый файл тоже очень просто. Просто передайте ссылку на этот метод методу max().
Скопируйте код кода следующим образом:
люди.поток()
.max(Человек::ageDifference)
.ifPresent(eldest -> System.out.println("Старший: " + старший));
Давайте посмотрим на имя и возраст старшего.
Скопируйте код кода следующим образом:
Старший: Грег, 35 лет.
Благодаря лямбда-выражениям и ссылкам на методы реализация компараторов становится проще и удобнее. JDK также добавляет в класс Comparor множество удобных методов, которые позволяют нам более плавно сравнивать, как мы увидим ниже.
Множественные сравнения и потоковые сравнения
Давайте рассмотрим новые удобные методы, предоставляемые интерфейсом Comparator, и воспользуемся ими для сравнения нескольких свойств.
Продолжим использовать пример из предыдущего раздела. Сортировка по названию, это то, что мы написали выше:
Скопируйте код кода следующим образом:
люди.поток()
.sorted((человек1, человек2) ->
person1.getName().compareTo(person2.getName()));
По сравнению с методом записи внутреннего класса прошлого века, этот метод записи слишком прост. Однако это можно упростить, если мы воспользуемся некоторыми функциями в классе Comparator. Использование этих функций позволит нам более плавно выразить нашу цель. Например, если мы хотим отсортировать по имени, мы можем написать:
Скопируйте код кода следующим образом:
окончательная функция <Person, String> byName = person -> person.getName();
люди.поток()
.sorted(сравнение(поИмени));
В этом коде мы импортировали статический метод Compare() класса Comparator. Метод сравнения() использует переданное лямбда-выражение для создания объекта Comparator. Другими словами, это также функция высшего порядка, которая принимает функцию в качестве входного параметра и возвращает другую функцию. Помимо того, что синтаксис становится более кратким, такой код также может лучше выразить реальную проблему, которую мы хотим решить.
Благодаря этому множественные сравнения могут стать более плавными. Например, следующий код сравнения по имени и возрасту говорит сам за себя:
Скопируйте код кода следующим образом:
окончательная функция<Person, Integer> byAge = person -> person.getAge();
окончательная функция<Person, String> byTheirName = person -> person.getName();
printPeople("Отсортировано по возрастанию и имени: ",
люди.поток()
.sorted(сравнение(по возрасту).thenComparing(поИмени))
.collect(toList()));
Сначала мы создали два лямбда-выражения, одно возвращает возраст указанного человека, а другое — его имя. При вызове метода sorted() мы объединяем эти два выражения, чтобы можно было сравнить несколько атрибутов. Метод сравнение() создает и возвращает компаратор на основе возраста. Затем мы вызываем метод thenComparing() для возвращенного компаратора, чтобы создать комбинированный компаратор, который сравнивает возраст и имя. Вывод ниже — это результат сортировки сначала по возрасту, а затем по имени.
Скопируйте код кода следующим образом:
Сортировано в порядке возрастания по возрасту и имени:
Джон - 20
Джейн - 21
Сара - 21
Грег – 35
Как видите, реализацию Comparator можно легко комбинировать с помощью лямбда-выражений и новых классов инструментов, предоставляемых JDK. Давайте представим Коллекционеров ниже.