1. 序章

Javaでの比較は、そうでない場合までは非常に簡単です。

カスタムタイプを使用する場合、または直接比較できないオブジェクトを比較しようとする場合は、比較戦略を利用する必要があります。 コンパレータまたはComparableインターフェースを使用するだけでビルドできます。

2. 例の設定

サッカーチームの例を使用してみましょう。ここでは、プレーヤーをランキングで並べます。

簡単なPlayerクラスを作成することから始めます。

public class Player {
    private int ranking;
    private String name;
    private int age;
    
    // constructor, getters, setters  
}

次に、 PlayerSorter クラスを作成してコレクションを作成し、Collections.sortを使用して並べ替えを試みます。

public static void main(String[] args) {
    List<Player> footballTeam = new ArrayList<>();
    Player player1 = new Player(59, "John", 20);
    Player player2 = new Player(67, "Roger", 22);
    Player player3 = new Player(45, "Steven", 24);
    footballTeam.add(player1);
    footballTeam.add(player2);
    footballTeam.add(player3);

    System.out.println("Before Sorting : " + footballTeam);
    Collections.sort(footballTeam);
    System.out.println("After Sorting : " + footballTeam);
}

予想どおり、これによりコンパイル時エラーが発生します。

The method sort(List<T>) in the type Collections 
  is not applicable for the arguments (ArrayList<Player>)

ここで、私たちが何を間違えたかを理解してみましょう。

3. 比較可能

名前が示すように、 Comparableは、オブジェクトを同じタイプの他のオブジェクトと比較する戦略を定義するインターフェースです。 これは、クラスの「自然順序付け」と呼ばれます。

並べ替えを可能にするには、 Compareable インターフェイスを実装して、Playerオブジェクトを比較可能として定義する必要があります。

public class Player implements Comparable<Player> {

    // same as before

    @Override
    public int compareTo(Player otherPlayer) {
        return Integer.compare(getRanking(), otherPlayer.getRanking());
    }

}

並べ替え順序は、compareTo()メソッドの戻り値によって決定されます。 Integer.compare(x、y)は、の場合に-1を返します。 xy未満であり、等しい場合は0、それ以外の場合は1です。

このメソッドは、比較されるオブジェクトが引数として渡されるオブジェクトよりも小さいか、等しいか、または大きいかを示す数値を返します。

PlayerSorter を実行すると、Playersがランキングで並べ替えられていることがわかります。

Before Sorting : [John, Roger, Steven]
After Sorting : [Steven, John, Roger]

Compareable による自然順序付けについて明確に理解できたので、インターフェースを直接実装するよりも柔軟な方法で他のタイプの順序付けを使用する方法を見てみましょう。

4. コンパレータ

コンパレータインターフェイスは、比較対象オブジェクトを表す2つの引数を使用してcompare(arg1、arg2)メソッドを定義し、 Compareable.compareTo()メソッドと同様に機能します。

4.1. コンパレータの作成

コンパレータを作成するには、コンパレータインターフェイスを実装する必要があります。

最初の例では、コンパレータを作成して、Playerranking属性を使用してプレーヤーを並べ替えます。

public class PlayerRankingComparator implements Comparator<Player> {

    @Override
    public int compare(Player firstPlayer, Player secondPlayer) {
       return Integer.compare(firstPlayer.getRanking(), secondPlayer.getRanking());
    }

}

同様に、コンパレータを作成して、Playerage属性を使用してプレーヤーを並べ替えることができます。

public class PlayerAgeComparator implements Comparator<Player> {

    @Override
    public int compare(Player firstPlayer, Player secondPlayer) {
       return Integer.compare(firstPlayer.getAge(), secondPlayer.getAge());
    }

}

4.2. コンパレータの動作

概念を示すために、実際にはコンパレータ使いたい。

このアプローチを使用して、自然な順序をオーバーライドできます

PlayerRankingComparator playerComparator = new PlayerRankingComparator();
Collections.sort(footballTeam, playerComparator);

次に、 PlayerRankingSorterをに実行して、結果を確認します。

Before Sorting : [John, Roger, Steven]
After Sorting by ranking : [Steven, John, Roger]

別の並べ替え順序が必要な場合は、使用しているコンパレータを変更するだけで済みます。

PlayerAgeComparator playerComparator = new PlayerAgeComparator();
Collections.sort(footballTeam, playerComparator);

PlayerAgeSorter を実行すると、 age:ごとに異なる並べ替え順序が表示されます。

Before Sorting : [John, Roger, Steven]
After Sorting by age : [Roger, John, Steven]

4.3. Java8コンパレータ

Java 8は、ラムダ式を使用してコンパレータを定義する新しい方法と、 compare()静的ファクトリメソッドを提供します。

ラムダ式を使用してコンパレータを作成する方法の簡単な例を見てみましょう。

Comparator byRanking = 
  (Player player1, Player player2) -> Integer.compare(player1.getRanking(), player2.getRanking());

コンパレータ.comparingメソッドは、アイテムの比較に使用されるプロパティを計算するメソッドを取り、一致するコンパレータインスタンスを返します。

Comparator<Player> byRanking = Comparator
  .comparing(Player::getRanking);
Comparator<Player> byAge = Comparator
  .comparing(Player::getAge);

Java 8の機能を詳細に調べるには、 Java 8Comparator.comparingガイドを確認してください。

5. コンパレータ比較可能

Comparableインターフェースは、デフォルトの順序を定義するために使用するのに適しています。つまり、オブジェクトを比較する主な方法である場合です。

では、すでに Compareable があるのに、なぜコンパレータを使用するのでしょうか。

理由はいくつかあります。

  • オブジェクトを並べ替えるクラスのソースコードを変更できない場合があります。そのため、Compareableを使用できません。
  • コンパレータを使用すると、ドメインクラスにコードを追加する必要がなくなります。
  • Compareable を使用する場合は不可能な、複数の異なる比較戦略を定義できます。

6. 減算トリックの回避

このチュートリアルでは、 Integer.compare()メソッドを使用して2つの整数を比較しました。 ただし、代わりにこの巧妙なワンライナーを使用する必要があると主張する人もいるかもしれません。

Comparator<Player> comparator = (p1, p2) -> p1.getRanking() - p2.getRanking();

他のソリューションよりもはるかに簡潔ですが、Javaの整数オーバーフローの犠牲になる可能性があります。

Player player1 = new Player(59, "John", Integer.MAX_VALUE);
Player player2 = new Player(67, "Roger", -1);

List<Player> players = Arrays.asList(player1, player2);
players.sort(comparator);

-1はInteger.MAX_VALUEよりもはるかに小さいため、ソートされたコレクションでは「Roger」が「John」の前に来る必要があります。 ただし、整数のオーバーフローのため、「Integer.MAX_VALUE –(-1)」はゼロ未満になります。 したがって、コンパレータ/比較可能なコントラクトに基づくと、 Integer.MAX_VALUE は-1未満であり、これは明らかに正しくありません。

したがって、私たちが期待したことにもかかわらず、ソートされたコレクションでは「ジョン」が「ロジャー」の前に来ます。

assertEquals("John", players.get(0).getName());
assertEquals("Roger", players.get(1).getName());

7. 結論

この記事では、CompareableおよびComparatorインターフェースについて説明し、それらの違いについて説明しました。

並べ替えのより高度なトピックを理解するには、 Java 8コンパレータJava8とLambdasの比較などの他の記事を確認してください。

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