Рубрики
Без рубрики

Руководство по компаратору Java 8.сравнение()

Практическое руководство по статическим функциям и методам экземпляра сопоставимого интерфейса, которые были представлены в Java 8.

Автор оригинала: baeldung.

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 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. Выбор ключа и Вариант компаратора

Существует еще один вариант, который облегчает переопределение естественного порядка ключа сортировки, предоставляя Компаратор , который создает пользовательский порядок для ключа сортировки:

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));
}

Как вы можете видеть, результаты сортируются в порядке убывания по имени :

[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 .