コンパレータインターフェイスを実装する
Comparator インターフェイスは、検索から並べ替え、逆の操作に至るまで、JDK ライブラリのあらゆる場所で見ることができます。 Java 8 では、これは関数型インターフェースになります。この利点は、ストリーミング構文を使用してコンパレーターを実装できることです。
新しい構文の値を確認するために、いくつかの異なる方法で Comparator を実装してみましょう。匿名の内部クラスを実装する必要がないため、キーストロークが大幅に節約されます。
コンパレータを使用した並べ替え
次の例では、さまざまな比較方法を使用して人のグループを並べ替えます。まず Person JavaBean を作成しましょう。
次のようにコードをコピーします。
パブリック クラス 人 {
プライベート最終文字列名。
プライベート最終年齢。
public Person(final String theName, Final int theAge) {
名前 = 名前;
年齢 = 年齢;
}
public String getName() { 名前を返す }
public int getAge() { 年齢を返す }
public int ageDifference(最終人物その他) {
年齢を返す - other.age;
}
public String toString() {
return String.format("%s - %d", 名前, 年齢);
}
}
Comparator インターフェイスは Person クラスを通じて実装できますが、この方法では使用できる比較メソッドは 1 つだけです。名前、年齢、またはこれらの組み合わせなど、さまざまな属性を比較できるようにしたいと考えています。比較を柔軟に実行するために、比較が必要なときに Comparator を使用して関連するコードを生成できます。
まず、名前と年齢が異なる人物のリストを作成しましょう。
次のようにコードをコピーします。
Final List<人> people = Arrays.asList(
新しい人物(「ジョン」、20)、
新しい人物(「サラ」、21)、
新しい人(「ジェーン」、21)、
新しい人("グレッグ", 35));
人々を名前または年齢で昇順または降順に並べ替えることができます。一般的な方法は、匿名の内部クラスを使用して Comparator インターフェイスを実装することです。このように書くと、より関連性の高いコードのみが意味を持ち、残りは単なる形式的なものになります。ラムダ式を使用すると、比較の本質に焦点を当てることができます。
まずは年齢順に最年少から最年長まで並べてみましょう。
List オブジェクトを取得したので、その sort() メソッドを使用して並べ替えることができます。ただし、この方法にも問題があります。これは void メソッドです。つまり、このメソッドを呼び出すとリストが変更されます。元のリストを保持するには、まずコピーを作成してから、sort() メソッドを呼び出す必要があります。あまりにも努力が多すぎた。現時点では、Stream クラスに助けを求める必要があります。
List から Stream オブジェクトを取得し、そのsorted() メソッドを呼び出すことができます。元のコレクションを変更するのではなく、並べ替えられたコレクションを返します。この方法を使用すると、コンパレータのパラメータを簡単に設定できます。
次のようにコードをコピーします。
List<人物> ascendingAge =
people.stream()
.sorted((人1, 人2) -> 人1.年齢差(人2))
.collect(toList());
printPeople("年齢の昇順に並べ替えます: ", ascendingAge);
まず、stream() メソッドを通じてリストを Stream オブジェクトに変換します。次に、sorted() メソッドを呼び出します。このメソッドは Comparator パラメータを受け入れます。 Comparator は関数型インターフェイスなので、ラムダ式を渡すことができます。最後に、collect メソッドを呼び出し、結果をリストに保存します。 collect メソッドは、反復プロセス中にオブジェクトを特定の形式または型に出力できるリデューサーです。 toList() メソッドは、Collectors クラスの静的メソッドです。
Comparator の抽象メソッド CompareTo() は、比較対象のオブジェクトである 2 つのパラメーターを受け取り、int 型の結果を返します。これと互換性を持たせるために、ラムダ式は 2 つのパラメーター (2 つの Person オブジェクト) も受け取ります。これらの型はコンパイラーによって自動的に推定されます。比較したオブジェクトが等しいかどうかを示す int 型を返します。
年齢で並べ替えたいため、2 つのオブジェクトの年齢を比較し、比較の結果を返します。同じサイズの場合は 0 を返します。それ以外の場合、最初の人が年下であれば負の数が返され、最初の人が年上であれば正の数が返されます。
sorted() メソッドは、ターゲット コレクションの各要素を走査し、指定された Comparator を呼び出して要素の並べ替え順序を決定します。 sorted() メソッドの実行方法は、前述のreduce() メソッドと多少似ています。 reduce() メソッドは、リストを結果に徐々に減らします。 sorted() メソッドは比較結果に基づいて並べ替えます。
並べ替えが完了したら、結果を出力したいので、printPeople() メソッドを呼び出します。このメソッドを実装しましょう。
次のようにコードをコピーします。
public static void printPeople(
最終文字列メッセージ、最終 List<人> 人) {
System.out.println(メッセージ);
people.forEach(System.out::println);
}
このメソッドでは、まずメッセージを出力し、次にリストを走査してその中の各要素を出力します。
sorted() メソッドを呼び出して、リスト内の人々を最年少から最年長の順に並べ替える方法を見てみましょう。
次のようにコードをコピーします。
年齢別に昇順に並べ替えると、次のようになります。
ジョン - 20
サラ - 21
ジェーン - 21
グレッグ - 35
改善するために、sorted() メソッドをもう一度見てみましょう。
次のようにコードをコピーします。
.sorted((人1, 人2) -> 人1.年齢差(人2))
渡されたラムダ式では、これら 2 つのパラメーターをルーティングするだけです。最初のパラメーターは ageDifference() メソッドの呼び出しターゲットとして使用され、2 番目のパラメーターはそのパラメーターとして使用されます。しかし、このように書くことはできず、オフィススペース モードを使用することになります。つまり、メソッド参照を使用し、Java コンパイラにルーティングを行わせることになります。
ここで使用されるパラメータ ルーティングは、前に見たものとは少し異なります。引数が呼び出しターゲットまたは呼び出しパラメーターとして渡されることを前に説明しました。ここで、2 つのパラメータがあり、それらを 2 つの部分に分割し、1 つはメソッド呼び出しのターゲットとして、もう 1 つはパラメータとして使用します。心配しないでください。Java コンパイラが「これは私が処理します」と教えてくれます。
以前のsorted()メソッドのラムダ式を短く簡潔なageDifferenceメソッドに置き換えることができます。
次のようにコードをコピーします。
people.stream()
.sorted(人物::年齢差)
このコードは、Java コンパイラによって提供されるメソッド参照のおかげで非常に簡潔です。コンパイラは 2 つの person インスタンス パラメータを受け取り、1 つ目は ageDifference() メソッドのターゲットとして使用し、2 つ目はメソッド パラメータとして使用します。コードを直接記述するのではなく、コンパイラーにこの作業を実行させます。このメソッドを使用するときは、最初のパラメーターが参照されるメソッドの呼び出しターゲットであり、残りのパラメーターがメソッドの入力パラメーターであることを確認する必要があります。
再利用コンパレータ
リスト内の人々を最年少から最年長の順に並べ替えるのも簡単です。また、最年長から最年少の順に並べ替えるのも簡単です。試してみましょう。
次のようにコードをコピーします。
printPeople("年齢の降順に並べ替えます: ",
people.stream()
.sorted((人1, 人2) -> 人2.年齢差(人1))
.collect(toList()));
前の例と同様に、sorted() メソッドを呼び出し、Comparator インターフェイスに適合するラムダ式を渡します。唯一の違いは、このラムダ式の実装です。比較する人々の順序を変更しています。結果は、年齢に基づいて最年長から最年少の順に並べる必要があります。見てみましょう。
次のようにコードをコピーします。
年齢別に降順に並べ替えると、次のようになります。
グレッグ - 35
サラ - 21
ジェーン - 21
ジョン - 20
比較ロジックを変更するだけなら、それほど手間はかかりません。ただし、パラメーターの順序がメソッド参照のパラメーター ルーティングの規則に準拠していないため、このバージョンをメソッド参照に再構築することはできません。最初のパラメーターはメソッドの呼び出しターゲットとしてではなく、メソッド パラメーターとして使用されます。この問題を解決し、重複した作業を減らす方法もあります。その方法を見てみましょう。
以前に 2 つのラムダ式を作成しました。1 つは年齢によって小さいものから大きいものにソートするもので、もう 1 つは大きいものから小さいものにソートするものです。そうすると、コードの冗長性と重複が生じ、DRY 原則に違反します。ソート順序を調整したいだけの場合、JDK はデフォルトで特別なメソッド修飾子を備えたリバース メソッドを提供します。これについては、77 ページのデフォルトのメソッドで説明します。ここでは、最初に reversed() メソッドを使用して冗長性を削除します。
次のようにコードをコピーします。
Comparator<人> CompareAscending =
(人1, 人2) -> 人1.年齢差(人2);
Comparator<人> CompareDescending = CompareAscending.reversed();
まず、人々を年齢別に最年少から最年長まで並べ替えるためのコンパレーター、compareAscending を作成しました。比較順序を逆にするには、このコードを再度記述する代わりに、最初の Comparator の reversed() メソッドを呼び出して 2 番目の Comparator オブジェクトを取得するだけです。 reversed() メソッドの内部では、比較されるパラメーターの順序を逆にするコンパレーターが作成されます。これは、 reversed も高階メソッドであることを示しています。つまり、副作用のない関数を作成して返します。コードではこれら 2 つのコンパレータを使用します。
次のようにコードをコピーします。
printPeople("年齢の昇順に並べ替えます: ",
people.stream()
.sorted(compareAscending)
.collect(toList())
);
printPeople("年齢の降順に並べ替えます: ",
people.stream()
.sorted(比較降順)
.collect(toList())
);
Java8 のこれらの新機能によりコードの冗長性と複雑さが大幅に軽減されたことがコードからはっきりとわかりますが、JDK には無限の可能性が待っています。
すでに年齢順に並べ替えることができますが、名前順に並べ替えるのも簡単です。同様に、ラムダ式のロジックを変更するだけで済みます。
次のようにコードをコピーします。
printPeople("名前の昇順に並べ替えます: ",
people.stream()
.sorted((人1, 人2) ->
person1.getName().compareTo(person2.getName()))
.collect(toList()));
出力結果は名前の辞書順に並べ替えられます。
次のようにコードをコピーします。
名前の昇順に並べ替えます。
グレッグ - 35
ジェーン - 21
ジョン - 20
サラ - 21
ここまでは年齢順または名前順に並べ替えてきました。ラムダ式のロジックをよりスマートにすることができます。たとえば、年齢と名前で同時に並べ替えることができます。
リストの中で最も若い人を選んでみましょう。まず年齢の小さいものから大きいものまで並べ替えてから、結果の最初のものを選択します。しかし、これは実際には機能しません。Stream にはこれを実現する min() メソッドがあります。このメソッドは Comparator も受け入れますが、コレクション内の最小のオブジェクトを返します。使ってみましょう。
次のようにコードをコピーします。
people.stream()
.min(人::年齢差)
.ifPresent(最年少 -> System.out.println("最年少: " + 最年少));
min() メソッドを呼び出すときは、ageDifference メソッド参照を使用しました。リストが空で、その中に複数の最年少者が存在する可能性があるため、 min() メソッドは Optinal オブジェクトを返します。次に、Optinal の ifPrsend() メソッドを通じて最年少の人を取得し、その詳細情報を出力します。出力を見てみましょう。
次のようにコードをコピーします。
最年少: ジョン - 20
最も古いものをエクスポートするのも非常に簡単です。このメソッド参照を max() メソッドに渡すだけです。
次のようにコードをコピーします。
people.stream()
.max(人::年齢差)
.ifPresent(長男 -> System.out.println("長男: " + 最年長));
長男の名前と年齢を見てみましょう。
次のようにコードをコピーします。
最年長: グレッグ - 35
ラムダ式とメソッド参照を使用すると、コンパレータの実装がより簡単かつ便利になります。 JDK では、Comparoror クラスに多くの便利なメソッドも導入されており、以下で説明するように、よりスムーズに比較できるようになります。
複数の比較とストリーミング比較
Comparator インターフェイスによって提供される便利な新しいメソッドを見て、それらを使用して複数のプロパティを比較してみましょう。
前のセクションの例を引き続き使用してみましょう。名前順に並べると、上で書いたことは次のようになります。
次のようにコードをコピーします。
people.stream()
.sorted((人1, 人2) ->
person1.getName().compareTo(person2.getName()));
前世紀の内部クラスの記述方法と比較すると、この記述方法は単純すぎます。ただし、Comparator クラスでいくつかの関数を使用すると、より簡単に目的を表現できるようになります。たとえば、名前で並べ替えたい場合は、次のように記述できます。
次のようにコードをコピーします。
Final Function<人, 文字列> byName = person -> person.getName();
people.stream()
.sorted(比較(名前));
このコードでは、Comparator クラスの静的メソッド Compareing() をインポートしました。 Compare() メソッドは、渡されたラムダ式を使用して Comparator オブジェクトを生成します。つまり、関数を入力パラメータとして受け取り、別の関数を返す高階関数でもあります。このようなコードは、構文をより簡潔にするだけでなく、解決したい実際の問題をより適切に表現することもできます。
これにより、複数の比較がよりスムーズになります。たとえば、名前と年齢で比較する次のコードがすべてを物語っています。
次のようにコードをコピーします。
Final Function<人, 整数> byAge = person -> person.getAge();
Final Function<人, 文字列> byTheirName = person -> person.getName();
printPeople("年齢と名前で昇順に並べ替えます: ",
people.stream()
.sorted(比較(年齢別).then比較(名前別))
.collect(toList()));
まず 2 つのラムダ式を作成しました。1 つは指定された人の年齢を返し、もう 1 つはその人の名前を返します。 sorted() メソッドを呼び出すときは、複数の属性を比較できるように、これら 2 つの式を組み合わせます。 Compare() メソッドは、年齢に基づいてコンパレーターを作成して返します。次に、返されたコンパレーターに対して thenComparing() メソッドを呼び出して、年齢と名前を比較する結合コンパレーターを作成します。以下の出力は、最初に年齢で並べ替え、次に名前で並べ替えた結果です。
次のようにコードをコピーします。
年齢と名前で昇順に並べ替えると、次のようになります。
ジョン - 20
ジェーン - 21
サラ - 21
グレッグ - 35
ご覧のとおり、Comparator の実装は、ラムダ式と JDK が提供する新しいツール クラスを使用して簡単に組み合わせることができます。以下にコレクターを紹介しましょう。