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() {
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. Выбор ключа и Вариант компаратора
Существует еще один вариант, который облегчает переопределение естественного порядка ключа сортировки, предоставляя Компаратор , который создает пользовательский порядок для ключа сортировки:
staticComparator 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));
} Как вы можете видеть, результаты сортируются в порядке убывания по имени :
[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() {
Comparator employeeNameComparator
= 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() {
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. Использование компаратора.
Аналогично тому, что мы сделали для ключей int , давайте рассмотрим пример использования Comparator.comparingLong для рассмотрения ключа сортировки типа long , упорядочив массив employees по полю mobile :
@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. Использование компаратора.
Опять же, аналогично тому, что мы сделали для ключей int и long , давайте рассмотрим пример использования Comparator.comparingDouble для рассмотрения ключа сортировки типа double , упорядочив массив employees по полю зарплата :
@Test
public void whenComparingDouble_thenSortedBySalary() {
Comparator employeeSalaryComparator
= 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() {
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. Использование Обратного Естественного Порядка
Аналогично естественному порядку , давайте используем метод ReverseOrder для создания Компаратора , который будет производить обратный порядок сотрудников по сравнению с тем, который приведен в примере 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. Учет нулевых значений в компараторе
В этом разделе рассматриваются функции 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() {
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. Считать Нулевым Последнее
Функция nulls Last вернет Компаратор , который сохраняет все 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. Использование компаратора.
Функция 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(){
Comparator employee_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() {
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)]
Аналогично, существуют функции then Comparing Long и then Comparing Double для использования long и double ключей сортировки.
7. Заключение
Эта статья представляет собой руководство по нескольким функциям, введенным в Java 8 для интерфейса Comparator .
Как обычно, исходный код можно найти на Github .