Java 8へのガイドComparator.comparing()

1概要

Java 8では、 Comparator インタフェースにいくつかの機能強化が導入されました。これには、コレクションのソート順を考え出すときに役立つ便利な静的関数がいくつか含まれています。

Java 8のラムダは、 Comparator インタフェースでも効果的に活用できます。ラムダと 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[]{...};

   //...
}

いつものように、完全なコードについてはhttps://github.com/eugenp/tutorials/tree/master/core-java[GitHubリンク]を参照してください。

3 Comparator.comparing を使用する

このセクションでは、 Comparator.comparing static関数の変形について説明します。

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

Comparator.comparing static関数はソートキー 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 Variant

ソートキーのカスタムの順序付けを作成する Comparator を提供することで、ソートキーの自然な順序付けを上書きするのを容易にする別のオプションがあります。

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

Comparator.comparing の2番目の引数として、名前を降順にソートするための Comparator を指定して、 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. Comparator.reversed を使用する

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

従業員が name の降順でソートされるように、従業員を name および reverse でソートする Comparator を使用します。

@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.comparing と同じことをする Comparator.comparingInt 関数もありますが、それは int セレクタだけを取ります。

employees age で並べる例でこれを試してみましょう。

@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 キーに対して行ったのと同様に、 compratorees 配列を mobile フィールドで順序付けることによって Comparator.comparingLong を使用して long 型のソートキーを検討する例を見てみましょう。

@Test
public void whenComparingLong__thenSortedByMobile() {
    Comparator<Employee> 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 キーに対して行ったのと同様に、 employees 配列を salary フィールドで並べて double 型のソートキーを検討するために Comparator.comparingDouble を使用した例を見てみましょう。

@Test
public void whenComparingDouble__thenSortedBySalary() {
    Comparator<Employee> 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 インターフェース実装の動作によって定義されます。 Comparator Comparable インターフェースの使用との違いについての詳細な情報は この記事の中 にあります。

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

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 フィールドに基づいて従業員を比較する上記のロジックを考えて、この関数を使用して employees 配列を自然な順序でソートする Comparator を取得しましょう。

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

@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コンパレータでのNULL値の考慮

このセクションでは、関数 nullsFirst および nullsLast について説明します。これらの関数は、順序付け時に 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 関数は、配列シーケンスの先頭にすべての 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));
}

ソート後の 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 関数は、並べ替えシーケンスの最後にすべての 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));
}

ソート後の 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)]----

辞書編集順序を name に続けて age に変更して、 thenComparing のもう1つのバージョン、 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));
}

並べ替え後に employees 配列の値がどのように並んでいるかを見てみましょう。T

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

同様に、 long および double ソートキーを使用するための関数 thenComparingLong および thenComparingDouble があります。

7. 結論

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

いつものように、ソースコードはhttps://github.com/eugenp/tutorials/tree/master/core-java-8[Githubに追加]を見つけることができます。