Руководство по Java 8 Comparator.comparing ()
1. обзор
Java 8 внесла несколько улучшений в интерфейсComparator, включая несколько статических функций, которые очень полезны при определении порядка сортировки для коллекций.
Лямбда-выражения Java 8 также могут эффективно использоваться с интерфейсомComparator. Подробное объяснение лямбда-выражений иComparator можно найти вhere, а хронику сортировки и примененияComparator можно найти вhere.
В этом руководствеwe will explore several functions introduced for the Comparator interface in Java 8.
2. Начиная
2.1. Пример класса компонента
Для примеров в этой статье давайте создадим bean-компонентEmployee и будем использовать его поля для сравнения и сортировки:
public class Employee {
String name;
int age;
double salary;
long mobile;
// constructors, getters & setters
}
2.2. Наши данные тестирования
Давайте также создадим массив сотрудников, который будет использоваться для хранения результатов нашего типа в различных тестовых примерах на протяжении всей статьи:
employees = new Employee[] { ... };
Первоначальный порядок элементовemployees будет следующим:
[Employee(name=John, age=25, salary=3000.0, mobile=9922001),
Employee(name=Ace, age=22, salary=2000.0, mobile=5924001),
Employee(name=Keith, age=35, salary=4000.0, mobile=3924401)]
На протяжении всей статьи мы будем сортировать массивEmployee, используя разные функции.
Для тестовых утверждений мы будем использовать набор предварительно отсортированных массивов, которые мы будем сравнивать с результатами нашей сортировки (то есть массивомemployees) для различных сценариев.
Объявим несколько из этих массивов:
@Before
public void initData() {
sortedEmployeesByName = new Employee[] {...};
sortedEmployeesByNameDesc = new Employee[] {...};
sortedEmployeesByAge = new Employee[] {...};
// ...
}
Как всегда, не стесняйтесь обращаться к нашемуGitHub link за полным кодом.
3. ИспользуяComparator.comparing
В этом разделе рассматриваются варианты статической функцииComparator.comparing.
3.1. Вариант селектора клавиш
Статическая функцияComparator.comparing принимает ключ сортировкиFunction и возвращаетComparator для типа, который содержит ключ сортировки:
static > Comparator comparing(
Function super T,? extends U> keyExtractor)
Чтобы увидеть это в действии, давайте используем полеname вEmployee в качестве ключа сортировки и передадим ссылку на его метод в качестве аргумента типаFunction.Comparator, возвращенный из того же используется для сортировки:
@Test
public void whenComparing_thenSortedByName() {
Comparator employeeNameComparator
= Comparator.comparing(Employee::getName);
Arrays.sort(employees, employeeNameComparator);
assertTrue(Arrays.equals(employees, sortedEmployeesByName));
}
Как видите, значения массиваemployees сортируются по имени в результате сортировки:
[Employee(name=Ace, age=22, salary=2000.0, mobile=5924001),
Employee(name=John, age=25, salary=3000.0, mobile=9922001),
Employee(name=Keith, age=35, salary=4000.0, mobile=3924401)]
3.2. Селектор клавиш и вариантComparator
Существует еще один вариант, который облегчает переопределение естественного порядка ключа сортировки, предоставляяComparator, которые создают настраиваемый порядок для ключа сортировки:
static Comparator comparing(
Function super T,? extends U> keyExtractor,
Comparator super U> keyComparator)
Давайте изменим вышеприведенный тест, заменив естественный порядок сортировки полемname, предоставивComparator для сортировки имен в порядке убывания в качестве второго аргумента дляComparator.comparing:
@Test
public void whenComparingWithComparator_thenSortedByNameDesc() {
Comparator employeeNameComparator
= Comparator.comparing(
Employee::getName, (s1, s2) -> {
return s2.compareTo(s1);
});
Arrays.sort(employees, employeeNameComparator);
assertTrue(Arrays.equals(employees, sortedEmployeesByNameDesc));
}
Как видите, результаты отсортированы в порядке убыванияname:
[Employee(name=Keith, age=35, salary=4000.0, mobile=3924401),
Employee(name=John, age=25, salary=3000.0, mobile=9922001),
Employee(name=Ace, age=22, salary=2000.0, mobile=5924001)]
3.3. ИспользуяComparator.reversed
При вызове существующегоComparator метод экземпляраComparator.reversed возвращает новыйComparator, который меняет порядок сортировки оригинала на обратный.
Давайте использоватьComparator, который сортирует сотрудников поname иreverse так, чтобы сотрудники были отсортированы в порядке убыванияname:
@Test
public void whenReversed_thenSortedByNameDesc() {
Comparator employeeNameComparator
= Comparator.comparing(Employee::getName);
Comparator employeeNameComparatorReversed
= employeeNameComparator.reversed();
Arrays.sort(employees, employeeNameComparatorReversed);
assertTrue(Arrays.equals(employees, sortedEmployeesByNameDesc));
}
Результаты отсортированы в порядке убыванияname:
[Employee(name=Keith, age=35, salary=4000.0, mobile=3924401),
Employee(name=John, age=25, salary=3000.0, mobile=9922001),
Employee(name=Ace, age=22, salary=2000.0, mobile=5924001)]
3.4. ИспользуяComparator.comparingInt
Также существует функцияComparator.comparingInt, которая делает то же самое, что иComparator.comparing, но использует только селекторыint. Давайте попробуем это на примере, где мы упорядочиваемemployees поage:
@Test
public void whenComparingInt_thenSortedByAge() {
Comparator employeeAgeComparator
= Comparator.comparingInt(Employee::getAge);
Arrays.sort(employees, employeeAgeComparator);
assertTrue(Arrays.equals(employees, sortedEmployeesByAge));
}
Давайте посмотрим, как значения массиваemployees упорядочиваются после сортировки:
[Employee(name=Ace, age=22, salary=2000.0, mobile=5924001),
Employee(name=John, age=25, salary=3000.0, mobile=9922001),
Employee(name=Keith, age=35, salary=4000.0, mobile=3924401)]
3.5. ИспользуяComparator.comparingLong
Подобно тому, что мы сделали для ключейint, давайте рассмотрим пример использованияComparator.comparingLong для рассмотрения ключа сортировки типаlong путем упорядочивания массиваemployees поmobileполе s:
@Test
public void whenComparingLong_thenSortedByMobile() {
Comparator employeeMobileComparator
= Comparator.comparingLong(Employee::getMobile);
Arrays.sort(employees, employeeMobileComparator);
assertTrue(Arrays.equals(employees, sortedEmployeesByMobile));
}
Давайте посмотрим, как значения массиваemployees упорядочиваются после сортировки сmobile в качестве ключа:
[Employee(name=Keith, age=35, salary=4000.0, mobile=3924401),
Employee(name=Ace, age=22, salary=2000.0, mobile=5924001),
Employee(name=John, age=25, salary=3000.0, mobile=9922001)]
3.6. ИспользуяComparator.comparingDouble
Опять же, аналогично тому, что мы сделали для ключейint иlong, давайте посмотрим пример использованияComparator.comparingDouble для рассмотрения ключа сортировки типаdouble, упорядочивemployees массивом поляsalary:
@Test
public void whenComparingDouble_thenSortedBySalary() {
Comparator employeeSalaryComparator
= Comparator.comparingDouble(Employee::getSalary);
Arrays.sort(employees, employeeSalaryComparator);
assertTrue(Arrays.equals(employees, sortedEmployeesBySalary));
}
Давайте посмотрим, как значения массиваemployees упорядочиваются после сортировки сsalary в качестве ключа сортировки:
[Employee(name=Ace, age=22, salary=2000.0, mobile=5924001),
Employee(name=John, age=25, salary=3000.0, mobile=9922001),
Employee(name=Keith, age=35, salary=4000.0, mobile=3924401)]
4. С учетом естественного порядка вComparator
Естественный порядок определяется поведением реализации интерфейсаComparable. Дополнительную информацию о разнице междуComparator и использованием интерфейсаComparable можно найти вin this article.
Давайте реализуемComparable в нашем классеEmployee, чтобы мы могли попробовать функцииnaturalOrder иreverseOrder интерфейсаComparator:
public class Employee implements Comparable{
// ...
@Override
public int compareTo(Employee argEmployee) {
return name.compareTo(argEmployee.getName());
}
}
4.1. Использование естественного порядка
ФункцияnaturalOrder возвращаетComparator для типа возврата, указанного в подписи:
static > Comparator naturalOrder()
Учитывая приведенную выше логику для сравнения сотрудников на основе поляname, давайте воспользуемся этой функцией для полученияComparator, который сортирует массивemployees в естественном порядке:
@Test
public void whenNaturalOrder_thenSortedByName() {
Comparator employeeNameComparator
= Comparator. naturalOrder();
Arrays.sort(employees, employeeNameComparator);
assertTrue(Arrays.equals(employees, sortedEmployeesByName));
}
Давайте посмотрим, как значения массиваemployees упорядочиваются после сортировки:
[Employee(name=Ace, age=22, salary=2000.0, mobile=5924001),
Employee(name=John, age=25, salary=3000.0, mobile=9922001),
Employee(name=Keith, age=35, salary=4000.0, mobile=3924401)]
4.2. Использование обратного естественного порядка
ПодобноnaturalOrder, давайте воспользуемся методомreverseOrder, чтобы сгенерироватьComparator, который произведет обратный порядокemployees к таковому в примереnaturalOrder:
@Test
public void whenReverseOrder_thenSortedByNameDesc() {
Comparator employeeNameComparator
= Comparator. reverseOrder();
Arrays.sort(employees, employeeNameComparator);
assertTrue(Arrays.equals(employees, sortedEmployeesByNameDesc));
}
Давайте посмотрим, как значения массиваemployees упорядочиваются после сортировки:
[Employee(name=Keith, age=35, salary=4000.0, mobile=3924401),
Employee(name=John, age=25, salary=3000.0, mobile=9922001),
Employee(name=Ace, age=22, salary=2000.0, mobile=5924001)]
5. Учет нулевых значений в компараторе
В этом разделе рассматриваются функцииnullsFirst иnullsLast, которые учитывают значенияnull при упорядочивании и сохраняют значенияnull в начале или конце последовательности упорядочивания.
5.1. Рассмотрение Null First
Давайте случайным образом вставим значенияnull в массивemployees:
[Employee(name=John, age=25, salary=3000.0, mobile=9922001),
null,
Employee(name=Ace, age=22, salary=2000.0, mobile=5924001),
null,
Employee(name=Keith, age=35, salary=4000.0, mobile=3924401)]
ФункцияnullsFirst вернетComparator, который хранит всеnulls в начале последовательности упорядочивания:
@Test
public void whenNullsFirst_thenSortedByNameWithNullsFirst() {
Comparator employeeNameComparator
= Comparator.comparing(Employee::getName);
Comparator employeeNameComparator_nullFirst
= Comparator.nullsFirst(employeeNameComparator);
Arrays.sort(employeesArrayWithNulls,
employeeNameComparator_nullFirst);
assertTrue(Arrays.equals(
employeesArrayWithNulls,
sortedEmployeesArray_WithNullsFirst));
}
Давайте посмотрим, как значения массиваemployees упорядочиваются после сортировки:
[null,
null,
Employee(name=Ace, age=22, salary=2000.0, mobile=5924001),
Employee(name=John, age=25, salary=3000.0, mobile=9922001),
Employee(name=Keith, age=35, salary=4000.0, mobile=3924401)]
5.2. Учитывая Null Last
ФункцияnullsLast вернетComparator, который сохраняет всеnulls в конце последовательности упорядочивания:
@Test
public void whenNullsLast_thenSortedByNameWithNullsLast() {
Comparator employeeNameComparator
= Comparator.comparing(Employee::getName);
Comparator employeeNameComparator_nullLast
= Comparator.nullsLast(employeeNameComparator);
Arrays.sort(employeesArrayWithNulls, employeeNameComparator_nullLast);
assertTrue(Arrays.equals(
employeesArrayWithNulls, sortedEmployeesArray_WithNullsLast));
}
Давайте посмотрим, как значения массиваemployees упорядочиваются после сортировки:
[Employee(name=Ace, age=22, salary=2000.0, mobile=5924001),
Employee(name=John, age=25, salary=3000.0, mobile=9922001),
Employee(name=Keith, age=35, salary=4000.0, mobile=3924401),
null,
null]
6. ИспользуяComparator.thenComparing
ФункцияthenComparing позволяет настроить лексикографический порядок значений, предоставив несколько ключей сортировки в определенной последовательности.
Рассмотрим еще один массив классаEmployee:
someMoreEmployees = new Employee[] { ... };
Рассмотрим следующую последовательность элементов в приведенном выше массиве:
[Employee(name=Jake, age=25, salary=3000.0, mobile=9922001),
Employee(name=Jake, age=22, salary=2000.0, mobile=5924001),
Employee(name=Ace, age=22, salary=3000.0, mobile=6423001),
Employee(name=Keith, age=35, salary=4000.0, mobile=3924401)]
Давайте запишем последовательность сравнений какage, за которой следуетname, и посмотрим, как упорядочивается этот массив:
@Test
public void whenThenComparing_thenSortedByAgeName(){
Comparator employee_Age_Name_Comparator
= Comparator.comparing(Employee::getAge)
.thenComparing(Employee::getName);
Arrays.sort(someMoreEmployees, employee_Age_Name_Comparator);
assertTrue(Arrays.equals(someMoreEmployees, sortedEmployeesByAgeName));
}
Здесь упорядочение будет выполненоage, а для значений с тем же самымage упорядочение будет выполненоname. Давайте посмотрим на это в последовательности, которую мы получаем после сортировки:
[Employee(name=Ace, age=22, salary=3000.0, mobile=6423001),
Employee(name=Jake, age=22, salary=2000.0, mobile=5924001),
Employee(name=Jake, age=25, salary=3000.0, mobile=9922001),
Employee(name=Keith, age=35, salary=4000.0, mobile=3924401)]
Давайте воспользуемся другой версиейthenComparing, то естьthenComparingInt, изменив лексикографическую последовательность наname, за которой следуетage:
@Test
public void whenThenComparing_thenSortedByNameAge() {
Comparator employee_Name_Age_Comparator
= Comparator.comparing(Employee::getName)
.thenComparingInt(Employee::getAge);
Arrays.sort(someMoreEmployees, employee_Name_Age_Comparator);
assertTrue(Arrays.equals(someMoreEmployees,
sortedEmployeesByNameAge));
}
Давайте посмотрим, как значения массиваemployees упорядочиваются после сортировки:
[Employee(name=Ace, age=22, salary=3000.0, mobile=6423001),
Employee(name=Jake, age=22, salary=2000.0, mobile=5924001),
Employee(name=Jake, age=25, salary=3000.0, mobile=9922001),
Employee(name=Keith, age=35, salary=4000.0, mobile=3924401)]
Точно так же есть функцииthenComparingLong иthenComparingDouble для использования ключей сортировкиlong иdouble.
7. Заключение
Эта статья представляет собой руководство по нескольким функциям, представленным в Java 8 для интерфейсаComparator.
Как обычно, исходный код можно найтиover on Github.