PR

java.lang.Comparableを初心者向けに1からわかりやすく

Java

java.lang.Comparableとは、Javaで「オブジェクト同士を比較して並び替えるための基準」を定めるためのインターフェースです。これを使えば、自分で作ったオブジェクトを簡単に並び替えたり、順序付けしたりできます。

参考 インターフェースとは?わかりやすく3分で解説

スポンサーリンク

Comparableとは?

プログラムでは「数値」や「文字列」の並び替えがよく行われます。数字なら「小さい順」「大きい順」、文字なら「あいうえお順」など、誰でも簡単にできますよね。

しかし、自分で作ったクラス(例えば人間、車、本)を並び替えるには、何を基準にするかを明確に決める必要があります。このときに役立つのが、Java標準のインターフェース「Comparable」です。

Comparableの基本的な使い方を理解するために、独自のクラスを定義して、そのクラスで並び順を変えるところまでを順を追って説明していきます。

Step①: クラスを作る

例えば「人間」というクラスを作ります。

class Person {
    String name; // 名前
    int age;     // 年齢

    Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
}

Step②: Comparableを実装する

「年齢で並べたい」とき、次のようにComparableを実装します。

class Person implements Comparable<Person> {
    String name;
    int age;

    Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    // これが比較を行うためのルールを決めるメソッド
    // 「Personクラスのインスタンス同士を、年齢(age)を使って比較する」
    @Override
    public int compareTo(Person other) {
        return Integer.compare(this.age, other.age);
    }
}

Comparableインターフェースは「並べ替えのルールを定めるために、compareTo()の実装を必須とする契約」です。この実装(ルール)に従って、Collections.sort()などがオブジェクトを並び替える、というイメージです。

1個1個整理しますね。

  • Comparableインターフェースの目的
    • 「このクラスのインスタンスを、自然な順序(natural order)で比較・並び替えできるようにする」ためのインターフェース。
  • compareToメソッドの役割
    • シグネチャは int compareTo(T other)
    • 戻り値の意味:
      • 負の値:thisother より小さい
      • 0:同じ
      • 正の値:thisother より大きい
    • ここに「並べ替えのルール」を書きます(例:年齢順、辞書順など)。
  • ルールを実装する流れ
    • クラス宣言で implements Comparable<YourClass> を付与
    • compareTo() をオーバーライドし、比較ロジックを記述
    • これでそのクラスは「Comparableなオブジェクト」となる
  • ルールに従った並び替え
    • Collections.sort(listOfYourClass);
    • Arrays.sort(arrayOfYourClass);
    • 上記を呼ぶと、自動的に compareTo() を使って順序付けされる

Java標準ライブラリ(OpenJDK)のソースから抜粋すると、java.lang.Comparable は非常にシンプルに定義されていることが分かります。

package java.lang;

/**
 * This interface imposes a total ordering on the objects of each
 * class that implements it.
 */
public interface Comparable<T> {
    /**
     * Compares this object with the specified object for order.
     * @param  o the object to be compared.
     * @return a negative integer, zero, or a positive integer
     *         as this object is less than, equal to, or greater than the specified object.
     */
    int compareTo(T o);
}

実際に並び替えをしてみよう(サンプルコード付き)

先ほど定義したPersonクラスを用いて実際に並び替えを行うコードを見てみましょう。以下は複数の人を年齢順に並べ替える例です。

import java.util.*;

public class Main {
    public static void main(String[] args) {
        List<Person> people = new ArrayList<>();
        people.add(new Person("佐藤", 25));
        people.add(new Person("鈴木", 20));
        people.add(new Person("高橋", 30));

        // 年齢順で並び替え
        Collections.sort(people);

        // 並び替えた結果を表示
        for (Person person : people) {
            System.out.println(person.name + " (" + person.age + ")");
        }
    }
}

/* 出力
鈴木 (20)
佐藤 (25)
高橋 (30) */

年齢の若い順に表示されます。

けど、なんで?っていうのをちょっとだけ深堀しておきます。

この1行:

Collections.sort(people);

なぜ並び替えをしてくれるのかの裏側の流れを見てみます。


🔁 流れを順を追って説明

ステップ① Collections.sort() を呼ぶ

Collections.sort(people);

これは、リストの中身を昇順(小さい順)に並び替えるメソッドです。
ただし、「どう並び替えるか(ルール)」は Comparable インターフェースの compareTo() に頼っています。

ステップ② 内部で List.sort(null) が呼ばれる

List.sort(null)の中身を見ると、実はこうなっています:

public static <T extends Comparable<? super T>> void sort(List<T> list) {
    list.sort(null); // Comparator が null だと \"自然順序\" でソート
}

これをやさしく言い換えると「Tは、自分自身か親クラスと比較できる能力(Comparable)を持った型でなければいけませんよ。そのリストを自然な順序で並べ替えます」ということ。<T extends Comparable<? super T>>の部分が肝でこれはジェネリクスの型制約と呼ばれます。

部分説明
TListに入っている要素の型(たとえば Person
Comparable<? super T>T またはその親クラスと比較できるという意味。共変性を許すための工夫です。

そして、リストの要素(この場合は Person)が Comparable を実装していれば、その compareTo() メソッドが呼び出されます。このとき内部ではこんな処理が行われています:

for (int i = 0; i < list.size(); i++) {
    for (int j = i + 1; j < list.size(); j++) {
        if (list.get(i).compareTo(list.get(j)) > 0) {
            // 要素を交換する(大きければ後ろに)
        }
    }
}

要するに compareTo() を使って2つのオブジェクトを比較し、順番を入れ替えているわけです。Collections.sort(people); だけで並び替えができる理由は:

  1. Personが Comparable を実装していて
  2. compareTo() で「年齢順に並べる」と明確に書かれており
  3. Javaが内部で compareTo() を何度も呼びながら順番を整えてくれる

という仕組みがあるからです。つまり、「compareToが並び替えのルールブックになっていて、Javaがそれを読んで並び替えている」と考えてOKです!

Comparatorとの違いを知ろう

Javaにはもう1つ「Comparator」という似た仕組みがあります。

  • Comparable
    自然な順序(通常の並べ方)をクラス自体が決定する場合に使います。
  • Comparator
    並び順を外側で自由に決めたい場合に使います(例えば名前順、年齢順などその都度変更する場合)。

Comparatorの使い方も知っておこう(応用編)

先ほどの「人」の例で、今度は名前順に並べたい場合、Comparatorを使います。

import java.util.*;

public class Main {
    public static void main(String[] args) {
        List<Person> people = new ArrayList<>();
        people.add(new Person("佐藤", 25));
        people.add(new Person("鈴木", 20));
        people.add(new Person("高橋", 30));

        // 名前順に並べ替え
        people.sort(new Comparator<Person>() {
            @Override
            public int compare(Person p1, Person p2) {
                return p1.name.compareTo(p2.name);
            }
        });

        for (Person person : people) {
            System.out.println(person.name + " (" + person.age + ")");
        }
    }
}

/* 出力
佐藤 (25)
鈴木 (20)
高橋 (30) */

今度は名前の50音順に並びました。

Comparableを使うメリット

  • わかりやすい:クラス自身がどのように並ぶかを決めているため、コードが読みやすいです。
  • 使いやすい:Javaの配列やリストのソート機能がそのまま使えます。

注意点(初心者がよく間違うポイント)

  • compareToメソッドを使うとき、数字を単純に引き算すると、整数オーバーフローを起こすことがあります。安全な方法は常に Integer.compare(a, b) を使うことです。

例:

良くない例:

return this.age - other.age; // オーバーフローの可能性あり

安全な例:

return Integer.compare(this.age, other.age); // 安全

まとめ

  • Comparableはオブジェクトの自然な順序を決定するための仕組み
  • compareTo()で比較方法を定義
  • Comparatorと使い分けることで、様々な並び替えが可能
  • 基本を押さえて、安全な書き方を心掛けることが大事
タイトルとURLをコピーしました