1. Обзор
Java 8 представила несколько улучшений интерфейса Comparator
, в том числе несколько статических функций, которые очень полезны при определении порядка сортировки коллекций.
Интерфейс Comparator
также может эффективно использовать лямбда-выражения Java 8. Подробное объяснение лямбда-выражений и компаратора
можно найти здесь , а хронику применения компаратора
и сортировки можно найти здесь .
В этом руководстве мы рассмотрим несколько функций, представленных для интерфейса Comparator
в 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[] { ... };
Начальный порядок элементов сотрудников
будет:
[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)]
На протяжении всего руководства мы будем сортировать приведенный выше массив сотрудников , используя различные функции.
Для тестовых утверждений мы будем использовать набор предварительно отсортированных массивов, которые мы будем сравнивать с нашими результатами сортировки (т . е. с массивом сотрудников
) для различных сценариев.
Давайте объявим несколько таких массивов:
@Before
public void initData() {
sortedEmployeesByName = new Employee[] {...};
sortedEmployeesByNameDesc = new Employee[] {...};
sortedEmployeesByAge = new Employee[] {...};
// ...
}
Как всегда, не стесняйтесь обращаться к нашей ссылке GitHub для получения полного кода.
3. Использование Comparator.comparing
В этом разделе мы рассмотрим варианты статической функции Comparator.comparing .
3.1. Вариант выбора ключа
Статическая функция Comparator.comparing принимает
функцию
ключа сортировки и возвращает Comparator
для типа, содержащего ключ сортировки:
static <T,U extends Comparable<? super U>> Comparator<T> comparing(
Function<? super T,? extends U> keyExtractor)
Чтобы увидеть это в действии, мы будем использовать поле имени
в Employee
в качестве ключа сортировки и передавать ссылку на его метод в качестве аргумента типа Function.
Компаратор ,
возвращенный из того же, используется для сортировки:
@Test
public void whenComparing_thenSortedByName() {
Comparator<Employee> employeeNameComparator
= Comparator.comparing(Employee::getName);
Arrays.sort(employees, employeeNameComparator);
assertTrue(Arrays.equals(employees, sortedEmployeesByName));
}
В результате сортировки значения массива сотрудников упорядочены по именам:
[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. Ключевой селектор и вариант компаратора
Есть еще один вариант, который облегчает переопределение естественного порядка ключа сортировки, предоставляя компаратор
, который создает пользовательский порядок для ключа сортировки:
static <T,U> Comparator<T> comparing(
Function<? super T,? extends U> keyExtractor,
Comparator<? super U> keyComparator)
Итак, давайте изменим тест выше. Мы переопределим естественный порядок сортировки по полю имени
, предоставив Comparator
для сортировки имен в порядке убывания в качестве второго аргумента Comparator.comparing
:
@Test
public void whenComparingWithComparator_thenSortedByNameDesc() {
Comparator<Employee> 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
При вызове существующего Comparator
метод экземпляра Comparator.reversed
возвращает новый Comparator
, который меняет порядок сортировки оригинала.
Мы будем использовать компаратор
, который сортирует сотрудников по именам
и реверсирует
его так, чтобы сотрудники сортировались в порядке убывания имени
:
@Test
public void whenReversed_thenSortedByNameDesc() {
Comparator<Employee> employeeNameComparator
= Comparator.comparing(Employee::getName);
Comparator<Employee> 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.comparingInt,
которая делает то же самое, что и Comparator.comparing
, но принимает только селекторы int .
Давайте попробуем это на примере, где мы упорядочиваем сотрудников
по возрасту
:
@Test
public void whenComparingInt_thenSortedByAge() {
Comparator<Employee> employeeAgeComparator
= Comparator.comparingInt(Employee::getAge);
Arrays.sort(employees, employeeAgeComparator);
assertTrue(Arrays.equals(employees, sortedEmployeesByAge));
}
После сортировки значения массива сотрудников имеют следующий порядок:
[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
путем упорядочения массива сотрудников по
мобильному
полю:
@Test
public void whenComparingLong_thenSortedByMobile() {
Comparator<Employee> employeeMobileComparator
= Comparator.comparingLong(Employee::getMobile);
Arrays.sort(employees, employeeMobileComparator);
assertTrue(Arrays.equals(employees, sortedEmployeesByMobile));
}
После сортировки значения массива сотрудников имеют следующий порядок с
мобильным телефоном
в качестве ключа:
[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
, упорядочивая массив сотрудников
по полю зарплаты :
@Test
public void whenComparingDouble_thenSortedBySalary() {
Comparator<Employee> employeeSalaryComparator
= Comparator.comparingDouble(Employee::getSalary);
Arrays.sort(employees, employeeSalaryComparator);
assertTrue(Arrays.equals(employees, sortedEmployeesBySalary));
}
После сортировки значения массива сотрудников имеют следующий порядок с
зарплатой
в качестве ключа сортировки:
[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 .
Более подробную информацию о различиях между Comparator
и использованием интерфейса Comparable
можно найти в этой статье .
Давайте реализуем Comparable
в нашем классе Employee
, чтобы мы могли попробовать функции naturalOrder
и reverseOrder
интерфейса Comparator
:
public class Employee implements Comparable<Employee>{
// ...
@Override
public int compareTo(Employee argEmployee) {
return name.compareTo(argEmployee.getName());
}
}
4.1. Использование естественного порядка
Функция naturalOrder
возвращает Comparator
для возвращаемого типа, указанного в подписи:
static <T extends Comparable<? super T>> Comparator<T> naturalOrder()
Учитывая приведенную выше логику для сравнения сотрудников на основе поля имени
, давайте используем эту функцию для получения компаратора
, который сортирует массив сотрудников
в естественном порядке:
@Test
public void whenNaturalOrder_thenSortedByName() {
Comparator<Employee> employeeNameComparator
= Comparator.<Employee> naturalOrder();
Arrays.sort(employees, employeeNameComparator);
assertTrue(Arrays.equals(employees, sortedEmployeesByName));
}
После сортировки значения массива сотрудников имеют следующий порядок:
[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
для создания компаратора
, который будет производить обратный порядок сотрудников
по сравнению с тем, что в примере naturalOrder
:
@Test
public void whenReverseOrder_thenSortedByNameDesc() {
Comparator<Employee> employeeNameComparator
= Comparator.<Employee> reverseOrder();
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)]
5. Учет нулевых значений в компараторе
В этом разделе мы рассмотрим функции nullsFirst
и nullsLast
, которые учитывают нулевые
значения при упорядочении и сохраняют нулевые
значения в начале или в конце упорядоченной последовательности.
5.1. С учетом нуля в первую очередь
Давайте случайным образом вставим нулевые
значения в массив сотрудников :
[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
вернет компаратор
, который сохраняет все нули
в начале последовательности упорядочивания:
@Test
public void whenNullsFirst_thenSortedByNameWithNullsFirst() {
Comparator<Employee> employeeNameComparator
= Comparator.comparing(Employee::getName);
Comparator<Employee> employeeNameComparator_nullFirst
= Comparator.nullsFirst(employeeNameComparator);
Arrays.sort(employeesArrayWithNulls,
employeeNameComparator_nullFirst);
assertTrue(Arrays.equals(
employeesArrayWithNulls,
sortedEmployeesArray_WithNullsFirst));
}
После сортировки значения массива сотрудников имеют следующий порядок:
[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. Учитывая нулевой последний
Функция nullsLast
вернет компаратор
, который сохраняет все нули
в конце последовательности упорядочения:
@Test
public void whenNullsLast_thenSortedByNameWithNullsLast() {
Comparator<Employee> employeeNameComparator
= Comparator.comparing(Employee::getName);
Comparator<Employee> employeeNameComparator_nullLast
= Comparator.nullsLast(employeeNameComparator);
Arrays.sort(employeesArrayWithNulls, employeeNameComparator_nullLast);
assertTrue(Arrays.equals(
employeesArrayWithNulls, sortedEmployeesArray_WithNullsLast));
}
После сортировки значения массива сотрудников имеют следующий порядок:
[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)]
Затем мы напишем последовательность сравнений в виде возраста
, за которым следует имя,
и посмотрим порядок этого массива:
@Test
public void whenThenComparing_thenSortedByAgeName(){
Comparator<Employee> 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)]
Теперь мы можем использовать другую версию thenComparing
, thenComparingInt
, изменив лексикографическую последовательность на имя
, за которым следует возраст
:
@Test
public void whenThenComparing_thenSortedByNameAge() {
Comparator<Employee> employee_Name_Age_Comparator
= Comparator.comparing(Employee::getName)
.thenComparingInt(Employee::getAge);
Arrays.sort(someMoreEmployees, employee_Name_Age_Comparator);
assertTrue(Arrays.equals(someMoreEmployees,
sortedEmployeesByNameAge));
}
После сортировки значения массива сотрудников имеют следующий порядок:
[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
предназначены для использования длинных
и двойных
ключей
сортировки соответственно.
7. Заключение
Эта статья представляет собой руководство по нескольким функциям, представленным в Java 8 для интерфейса Comparator .
Как обычно, исходный код можно найти на Github .