1. 概要

Java 8では、コンパレータインターフェースにいくつかの拡張機能が導入されました。これには、コレクションのソート順を作成するときに非常に役立ついくつかの静的関数が含まれます。

コンパレータインターフェースは、Java8ラムダを効果的に活用することもできます。 ラムダとコンパレータの詳細な説明はここにあり、コンパレータのアプリケーションと並べ替えの記録はここにあります。 。

このチュートリアルでは、 Java8のコンパレータインターフェイスに導入されたいくつかの機能について説明します。

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[] { ... };

従業員の要素の最初の順序は次のようになります。

[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配列を並べ替えます。

テストアサーションでは、事前に並べ替えられた配列のセットを使用して、さまざまなシナリオの並べ替え結果(つまり、 employee 配列)と比較します。

これらの配列のいくつかを宣言しましょう:

@Before
public void initData() {
    sortedEmployeesByName = new Employee[] {...};
    sortedEmployeesByNameDesc = new Employee[] {...};
    sortedEmployeesByAge = new Employee[] {...};
    
    // ...
}

いつものように、完全なコードについては、GitHubリンクを参照してください。

3. Comparator.comparingを使用する

このセクションでは、コンパレータ.comparing静的関数のバリエーションについて説明します。

3.1. キーセレクターバリアント

コンパレータ.comparing静的関数は、ソートキー関数を受け入れ、ソートキーを含むタイプのコンパレータを返します。

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

これが実際に動作することを確認するために、Employeenameフィールドを並べ替えキーとして使用し、そのメソッド参照をタイプ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.comparing の2番目の引数として、名前を降順で並べ替えるためのコンパレータを提供することにより、nameフィールドによる並べ替えの自然な順序をオーバーライドします。 :

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

ご覧のとおり、結果は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. コンパレータの使用.reversed

既存のComparatorで呼び出されると、インスタンスメソッド Comparator.reversed は、元のソート順を逆にする新しいコンパレータを返します。

従業員を名前で並べ替えるコンパレータを使用して、従業員が名前の降順で並べ替えられるようにします。 ]:

@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を使用する

コンパレータ.comparingInt、という関数もあります。この関数はコンパレータ.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タイプのソートキーを検討する例を見てみましょう。 mobile フィールドによるemployees配列:

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

並べ替え後、従業員の配列の値は、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. Comparison.comparingDoubleを使用する

ここでも、intおよびlongキーの場合と同様に、Comparator.comparingDoubleを使用してタイプdouble[のソートキーを検討する例を見てみましょう。 employee配列をsalaryフィールドで並べ替えます。

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

並べ替え後、従業員の配列値は、並べ替えキーとして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. コンパレータで自然秩序を考慮する

Compareable インターフェイス実装の動作によって、自然な順序を定義できます。 コンパレータ比較可能インターフェースの使用法の違いの詳細については、この記事を参照してください。

[X13X]EmployeeクラスにComparableを実装して、コンパレータnaturalOrderおよびreverseOrder関数を試してみましょう。インターフェース:

public class Employee implements Comparable<Employee>{
    // ...

    @Override
    public int compareTo(Employee argEmployee) {
        return name.compareTo(argEmployee.getName());
    }
}

4.1. 自然秩序の使用

naturalOrder 関数は、署名に記載されている戻りタイプのコンパレータを返します。

static <T extends Comparable<? super T>> Comparator<T> naturalOrder()

name フィールドに基づいて従業員を比較する上記のロジックを前提として、この関数を使用して、employee配列を自然な順序で並べ替えるComparatorを取得しましょう。

@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関数について説明します。これらの関数は、 null 値を順番に考慮し、null値を保持します。注文シーケンスの最初または最後。

5.1. 最初にヌルを検討する

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 関数は、順序付けシーケンスの最初にすべてのnullsを保持するComparatorを返します。

@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 関数は、順序付けシーケンスの最後にすべてのnullsを保持するComparatorを返します。

@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)]

次に、一連の比較を 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)]

これで、辞書式順序を nameに続けてageに変更することにより、他のバージョンの 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は、それぞれlongおよびdoubleソートキーを使用するためのものです。

7. 結論

この記事は、コンパレータインターフェース用にJava8で導入されたいくつかの機能のガイドです。

いつものように、ソースコードはGithubにあります。