1. Обзор
Java 8 представила несколько улучшений интерфейса Comparator , включая несколько статических функций, которые очень полезны при создании порядка сортировки для коллекций.
Лямбды Java 8 также можно эффективно использовать с помощью интерфейса Comparator . Подробное объяснение лямбд и Компаратора можно найти здесь , а хронику сортировки и применения Компаратора можно найти здесь .
В этом уроке мы рассмотрим несколько функций, введенных для интерфейса Comparator в Java 8 .
2. Начало работы
2.1. Пример класса Бобов
Для примеров в этой статье давайте создадим Employee bean и будем использовать его поля для сравнения и сортировки:
public class Employee { String name; int age; double salary; long mobile; // constructors, getters & setters }
2.2. Наши Данные Тестирования
Давайте также создадим массив сотрудников, который будет использоваться для хранения результатов нашего типа в различных тестовых случаях на протяжении всей статьи:
employees = new Employee[] { ... };
Первоначальное упорядочение элементов сотрудников будет:
[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 для получения полного кода.
3. Использование компаратора.
В этом разделе рассматриваются варианты функции Comparator.comparing static.
3.1. Вариант выбора Ключа
Функция Comparator.comparing static принимает ключ сортировки Function и возвращает Comparator для типа, содержащего ключ сортировки:
static> Comparator comparing( Function super T,? extends U> keyExtractor)
Чтобы увидеть это в действии, давайте использовать поле name в Employee в качестве ключа сортировки и передадим ссылку на его метод в качестве аргумента типа Function. Компаратор , возвращенный из того же самого, используется для сортировки:
@Test public void whenComparing_thenSortedByName() { ComparatoremployeeNameComparator = 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. Выбор ключа и Вариант компаратора
Существует еще один вариант, который облегчает переопределение естественного порядка ключа сортировки, предоставляя Компаратор , который создает пользовательский порядок для ключа сортировки:
staticComparator comparing( Function super T,? extends U> keyExtractor, Comparator super U> keyComparator)
Давайте изменим приведенный выше тест, переопределив естественный порядок сортировки по полю name , предоставив Comparator для сортировки имен в порядке убывания в качестве второго аргумента Comparator.comparing :
@Test public void whenComparingWithComparator_thenSortedByNameDesc() { ComparatoremployeeNameComparator = Comparator.comparing( Employee::getName, (s1, s2) -> { return s2.compareTo(s1); }); Arrays.sort(employees, employeeNameComparator); assertTrue(Arrays.equals(employees, sortedEmployeesByNameDesc)); }
Как вы можете видеть, результаты сортируются в порядке убывания по имени :
[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 возвращает новый Компаратор , который изменяет порядок сортировки оригинала.
Давайте используем Компаратор , который сортирует сотрудников по имени и обратному , чтобы сотрудники сортировались в порядке убывания имени :
@Test public void whenReversed_thenSortedByNameDesc() { ComparatoremployeeNameComparator = Comparator.comparing(Employee::getName); Comparator employeeNameComparatorReversed = employeeNameComparator.reversed(); Arrays.sort(employees, employeeNameComparatorReversed); assertTrue(Arrays.equals(employees, sortedEmployeesByNameDesc)); }
Результаты сортируются в порядке убывания по имени :
[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.comparing , но она принимает только int селекторы. Давайте попробуем это на примере, где мы заказываем сотрудников по возрасту :
@Test public void whenComparingInt_thenSortedByAge() { ComparatoremployeeAgeComparator = 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. Использование компаратора.
Аналогично тому, что мы сделали для ключей int , давайте рассмотрим пример использования Comparator.comparingLong для рассмотрения ключа сортировки типа long , упорядочив массив employees по полю mobile :
@Test public void whenComparingLong_thenSortedByMobile() { ComparatoremployeeMobileComparator = 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. Использование компаратора.
Опять же, аналогично тому, что мы сделали для ключей int и long , давайте рассмотрим пример использования Comparator.comparingDouble для рассмотрения ключа сортировки типа double , упорядочив массив employees по полю зарплата :
@Test public void whenComparingDouble_thenSortedBySalary() { ComparatoremployeeSalaryComparator = Comparator.comparingDouble(Employee::getSalary); Arrays.sort(employees, employeeSalaryComparator); assertTrue(Arrays.equals(employees, sortedEmployeesBySalary)); }
Давайте посмотрим, как упорядочиваются значения массива 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. Учет естественного порядка в компараторе
Естественный порядок определяется поведением реализации интерфейса Comparable . Более подробную информацию о разнице между Компаратором и использованием Сопоставимого интерфейса можно найти в этой статье .
Давайте реализуем Comparable в нашем классе Employee , чтобы мы могли попробовать естественный порядок и обратный порядок функций интерфейса Comparator :
public class Employee implements Comparable{ // ... @Override public int compareTo(Employee argEmployee) { return name.compareTo(argEmployee.getName()); } }
4.1. Использование Естественного порядка
Функция естественный порядок возвращает Компаратор для типа возврата, указанного в подписи:
static> Comparator naturalOrder()
Учитывая приведенную выше логику сравнения сотрудников на основе поля name , давайте используем эту функцию для получения Компаратора , который сортирует массив employees в естественном порядке:
@Test public void whenNaturalOrder_thenSortedByName() { ComparatoremployeeNameComparator = 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. Использование Обратного Естественного Порядка
Аналогично естественному порядку , давайте используем метод ReverseOrder для создания Компаратора , который будет производить обратный порядок сотрудников по сравнению с тем, который приведен в примере naturalOrder :
@Test public void whenReverseOrder_thenSortedByNameDesc() { ComparatoremployeeNameComparator = 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. Учет нулевых значений в компараторе
В этом разделе рассматриваются функции nulls First и nulls Last , которые учитывают null значения в порядке и сохраняют null значения в начале или конце последовательности упорядочения.
5.1. Сначала Рассмотрим Null
Давайте случайным образом вставим 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)]
Функция nulls First вернет Компаратор , который сохраняет все nulls в начале последовательности упорядочения:
@Test public void whenNullsFirst_thenSortedByNameWithNullsFirst() { ComparatoremployeeNameComparator = 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. Считать Нулевым Последнее
Функция nulls Last вернет Компаратор , который сохраняет все nulls в конце последовательности упорядочения:
@Test public void whenNullsLast_thenSortedByNameWithNullsLast() { ComparatoremployeeNameComparator = 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. Использование компаратора.
Функция 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)]
Давайте запишем последовательность сравнений как возраст с последующим именем и посмотрим порядок этого массива:
@Test public void whenThenComparing_thenSortedByAgeName(){ Comparatoremployee_Age_Name_Comparator = Comparator.comparing(Employee::getAge) .thenComparing(Employee::getName); Arrays.sort(someMoreEmployees, employee_Age_Name_Comparator); assertTrue(Arrays.equals(someMoreEmployees, sortedEmployeesByAgeName)); }
Здесь заказ будет выполнен по возрасту , а для значений с одинаковым возрастом заказ будет выполнен по имени . Давайте рассмотрим это в последовательности, которую мы получаем после сортировки:
[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)]
Давайте воспользуемся другой версией , затем сравним , то есть Затем сравним , изменив лексикографическую последовательность на имя с последующим возрастом :
@Test public void whenThenComparing_thenSortedByNameAge() { Comparatoremployee_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)]
Аналогично, существуют функции then Comparing Long и then Comparing Double для использования long и double ключей сортировки.
7. Заключение
Эта статья представляет собой руководство по нескольким функциям, введенным в Java 8 для интерфейса Comparator .
Как обычно, исходный код можно найти на Github .