1. 導入:なぜComparatorが必要なのか?
Javaでプログラミングをしていると、「リストの中身を特定の条件で並び替えたい」という場面によく遭遇します。例えば、ユーザー名で昇順に並べたり、年齢で降順に並べたりといったケースです。
通常、オブジェクトを並び替えるにはComparableインターフェースを使いますが、これは「そのクラスが本来持っている順序」を定義するものです。しかし、「状況に応じて異なる並び替えルールを適用したい」という場合、クラスを書き換えるのは非効率です。ここで登場するのが「java.util.Comparator」です。これを使うことで、元のクラスを汚さずに、外部から自由な「比較の戦略」を注入できるようになります。
2. 基礎知識:Comparatorとは何か?
Comparator(コンパレータ)は、「2つのオブジェクトを比較して、どちらが先か後か(あるいは等しいか)を判定する」ためのインターフェースです。
比較のルールをメソッドとして定義し、それをCollections.sort()やList.sort()に渡すことで、並び替えのロジックを切り替えることができます。
また、Java 8以降はラムダ式と組み合わせることで、非常に簡潔に記述できるようになりました。比較には、算術演算(引き算など)や、論理演算(if文や三項演算子)を組み合わせるのが一般的です。
3. 実装/解決策:Comparatorの基本パターン
Comparatorを実装するには、compare(T o1, T o2)メソッドを定義します。このメソッドの戻り値にはルールがあります。
・戻り値が負(マイナス):o1がo2より前
・戻り値が0:o1とo2は等しい
・戻り値が正(プラス):o1がo2より後ろ
この「引き算の結果を返す」という仕組みを理解すると、実装が非常にスムーズになります。
4. サンプルプログラム:年齢で並び替える例
以下のコードは、Userクラスを年齢順に並び替える実例です。コピー&ペーストしてそのまま動作確認が可能です。
import java.util.;
public class Main {
// 並び替え対象のクラス
static class User {
String name;
int age;
User(String name, int age) { this.name = name; this.age = age; }
@Override public String toString() { return name + "(" + age + ")"; }
}
public static void main(String[] args) {
List<User> users = new ArrayList<>();
users.add(new User("佐藤", 30));
users.add(new User("田中", 20));
users.add(new User("鈴木", 25));
// Comparatorをラムダ式で定義(年齢の昇順)
// (o1, o2) -> o1.age - o2.age と同じ論理です
users.sort((o1, o2) -> Integer.compare(o1.age, o2.age));
System.out.println("年齢順: " + users);
// 応用:Comparator.comparingを使うとより直感的です
users.sort(Comparator.comparing(u -> u.name));
System.out.println("名前順: " + users);
}
}
5. 応用・注意点:現場で陥りやすい罠
現場でComparatorを使う際に最も注意すべきは「オーバーフロー」です。
例えば、int型の値を比較する際に「o1.value – o2.value」という引き算をすると、値が非常に大きい場合や小さい場合に、計算結果がintの範囲を超えてしまい、正しく比較できないバグが発生します。
これを回避するために、Integer.compare(x, y) や Double.compare(x, y) といった標準ライブラリのメソッドを使うのがベストプラクティスです。
また、Java 16以降で導入された「instanceofによるパターンマッチング」を使うと、Comparator内で型チェックをする際に記述を短縮できます。
例えば「if (obj instanceof User u)」のように書くことで、キャストの手間を省き、コードの安全性と可読性を高めることができます。ぜひ活用してください。

コメント