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に追加]を見つけることができます。