Java 8 Comparator.comparing() 指南

评论 0 浏览 0 2017-03-30

1.概述

Java 8对Comparator接口引入了一些增强功能,包括一些静态函数,这些函数在为集合制定排序顺序时具有很大的作用。

Comparator 接口也可以有效地利用Java 8的lambdas。关于lambdas和Comparator的详细解释可以在这里找到,关于Comparator和排序的应用纪实可以在这里找到。

在本教程中,我们将探讨为Java 8中的Comparator接口引入的几个函数。

2.入门

2.1.示例 Bean 类

在本教程的例子中,让我们创建一个Employee Bean,并使用它的字段进行比较和排序。

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链接,以获取完整的代码。

3.使用Comparator.comparing

在这一节中,我们将介绍Comparator.comparing静态函数的变体。

3.1.键选择器变体

Comparator.comparing静态函数接受一个排序键Function,并返回一个包含排序键的类型的Comparator

static <T,U extends Comparable<? super U>> Comparator<T> comparing(
   Function<? super T,? extends U> keyExtractor)

为了看到这个动作,我们将使用Employee中的name字段作为排序键,并将其方法引用作为Function类型的参数传递给它,从同一个返回的Comparator被用来进行排序。

@Test
public void whenComparing_thenSortedByName() {
    Comparator<Employee> 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 <T,U> Comparator<T> comparing(
  Function<? super T,? extends U> keyExtractor,
    Comparator<? super U> keyComparator)

因此,让我们修改上面的测试。我们将通过提供一个Comparator来覆盖按name字段排序的自然顺序,作为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,该方法颠覆了原来的排序顺序。

我们将使用Comparator,按name对雇员进行排序,并reverse,使雇员按name的降序进行排序。

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

现在,结果是按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选择器。让我们用一个例子来试试,我们用employeesage排序:

@Test
public void whenComparingInt_thenSortedByAge() {
    Comparator<Employee> 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字段进行排序。

@Test
public void whenComparingLong_thenSortedByMobile() {
    Comparator<Employee> employeeMobileComparator 
      = Comparator.comparingLong(Employee::getMobile);
    
    Arrays.sort(employees, employeeMobileComparator);
    
    assertTrue(Arrays.equals(employees, sortedEmployeesByMobile));
}

在排序之后,employees array的值有以下的顺序,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

同样,正如我们对intlong键所做的那样,让我们看一个使用Comparator.comparingDouble的例子,来考虑一个double类型的排序键,通过对employees数组按salary字段排序:

@Test
public void whenComparingDouble_thenSortedBySalary() {
    Comparator<Employee> employeeSalaryComparator
      = Comparator.comparingDouble(Employee::getSalary);
    
    Arrays.sort(employees, employeeSalaryComparator);
    
    assertTrue(Arrays.equals(employees, sortedEmployeesBySalary));
}

在排序之后,employees array的值有以下的顺序,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 接口的使用的更多信息,请参阅这篇文章

让我们在我们的Employee类中实现Comparable,这样我们就可以尝试Comparator接口的naturalOrderreverseOrder功能了:

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()

鉴于上述基于name字段比较雇员的逻辑,让我们使用这个函数来获得一个Comparator,该函数将employees数组按自然顺序排序:

@Test
public void whenNaturalOrder_thenSortedByName() {
    Comparator<Employee> employeeNameComparator 
      = Comparator.<Employee> 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,与naturalOrder例子中的employees相比,它将产生一个反向的排序:

@Test
public void whenReverseOrder_thenSortedByNameDesc() {
    Comparator<Employee> employeeNameComparator 
      = Comparator.<Employee> 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.考虑比较器中的空值

在本节中,我们将介绍nullsFirstnullsLast函数,它们在排序时考虑null值,并将null值保留在排序序列的开头或结尾。

5.1.首先考虑空值

让我们在employees数组中随机插入null的值。

[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<Employee> employeeNameComparator
      = Comparator.comparing(Employee::getName);
    Comparator<Employee> 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

nullsLast函数将返回一个Comparator,该函数将所有的nulls保留在排序序列的最后:

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

在排序之后,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> 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> 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)]

类似地,函数thenComparingLongthenComparingDouble分别用于使用longdouble的排序键

7.结语

本文是关于Java 8中引入的Comparator接口的几项功能的指南。

像往常一样,源代码可以在Github上找到。

最后更新2023-04-03
0 个评论
标签