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 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 linkを自由に参照してください。

3. Comparator.comparingの使用

このセクションでは、Comparator.comparing静的関数のバリアントについて説明します。

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

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

static > Comparator comparing(
   Function keyExtractor)

これが実際に動作することを確認するために、Employeenameフィールドをソートキーとして使用し、そのメソッド参照をタイプ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)

上記のテストを変更して、Comparator.comparingの2番目の引数として名前を降順で並べ替えるためのComparatorを提供することにより、nameフィールドによる並べ替えの自然な順序をオーバーライドしてみましょう。

@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を返します。

従業員をnameで並べ替えるComparatorreverseを使用して、従業員が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.comparingと同じことを行う関数Comparator.comparingIntもありますが、必要なのはintセレクターだけです。 employeesageで並べ替える例でこれを試してみましょう。

@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を使用して、employees配列をmobileで並べ替えることにより、タイプlongのソートキーを検討する例を見てみましょう。 sフィールド:

@Test
public void whenComparingLong_thenSortedByMobile() {
    Comparator employeeMobileComparator
      = Comparator.comparingLong(Employee::getMobile);

    Arrays.sort(employees, employeeMobileComparator);

    assertTrue(Arrays.equals(employees, sortedEmployeesByMobile));
}

mobileをキーとして、並べ替え後にemployees配列値がどのように順序付けられるかを見てみましょう。

[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を使用して、employeesを順序付けてタイプdoubleのソートキーを検討する例を見てみましょう。 ssalaryフィールドによる配列:

@Test
public void whenComparingDouble_thenSortedBySalary() {
    Comparator employeeSalaryComparator
      = Comparator.comparingDouble(Employee::getSalary);

    Arrays.sort(employees, employeeSalaryComparator);

    assertTrue(Arrays.equals(employees, sortedEmployeesBySalary));
}

ソートキーとしてsalaryを使用して、ソート後に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. Comparatorの自然順序を考慮する

自然な順序は、Comparableインターフェイス実装の動作によって定義されます。 ComparatorComparableインターフェースの使用法の違いの詳細については、in this articleを参照してください。

ComparatorインターフェイスのnaturalOrder関数とreverseOrder関数を試すことができるように、EmployeeクラスにComparableを実装しましょう。

public class Employee implements Comparable{
    // ...

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

4.1. 自然秩序の使用

naturalOrder関数は、シグネチャに記載されている戻り値の型のComparatorを返します。

static > Comparator naturalOrder()

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

@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を生成します。これにより、naturalOrderの例とは逆の順序でemployeesが生成されます。

@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を検討する

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

@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. 最後にヌルを考慮する

nullsLast関数は、すべてのnullsを順序付けシーケンスの最後に保持するComparatorを返します。

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

辞書式順序をnameに続けてageに変更することにより、thenComparingの他のバージョンであるthenComparingIntを使用してみましょう。

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

同様に、longdoubleのソートキーを使用するための関数thenComparingLongthenComparingDoubleがあります。

7. 結論

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

いつものように、ソースコードはover on Githubで見つけることができます。