Руководство по Java 8 Comparator.comparing ()

Руководство по 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 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 keyExtractor,
    Comparator 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.