Java compareTo ordering logic and Comparable natural order technical banner

Java compareToの使い方と戻り値を実装例で解説

目次

1. JavaのcompareToで何を比較するのか

【結論】compareToは、2つのオブジェクトの「並び順」を決めるためのメソッドです。等しいかどうかだけでなく、前に置くか、同じ順序か、後ろに置くかを整数の符号で表します。

1.1 compareToは順序を返すメソッド

【結論】compareToComparable<T>インターフェースに定義されているメソッドです。戻り値の符号を使って自然順序を表します。

【定義】自然順序とは、そのクラスが標準として持つ並び順です。Stringなら辞書式順序、Integerなら数値順のように、クラス自身が自然だと定義する順序を指します。

Comparable<T>の基本形は次のとおりです。

public interface Comparable<T> {
    int compareTo(T other);
}

OracleのAPI仕様では、ComparablecompareToがクラスの自然順序を表すと説明されています。仕様を確認したい場合はComparable Java API Documentationを参照してください。

1.2 compareToはソート処理の土台になる

【結論】compareToは、Collections.sortList.sortArrays.sortなどの並び替えで使われます。独自クラスを自然な順序で並べたい場合は、Comparableを実装します。

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

List<String> names = new ArrayList<>();
names.add("Sato");
names.add("Aoki");
names.add("Tanaka");

Collections.sort(names);
System.out.println(names); // [Aoki, Sato, Tanaka]

この例では、StringがすでにComparable<String>を実装しているため、リストを自然順序で並べ替えられます。

1.3 equalsとの違いを最初に分ける

【結論】equalsは同一性や等価性の判定、compareToは順序の判定です。compareToが0を返すことと、equalsがtrueを返すことは、設計上そろえるのが望ましい場面が多いです。

メソッド目的戻り値主な利用場面
equals同じ値として扱うかboolean重複判定、同値判定
compareToどちらを前に並べるかintソート、TreeSetTreeMap

TreeSetTreeMapは順序に基づいて要素を扱います。compareToが0を返すのにequalsがfalseになる設計では、重複していないと思った要素が追加されないなど、初心者には分かりにくい挙動になります。

2. compareToの戻り値を正しく読む

【結論】compareToの戻り値は「負の整数」「0」「正の整数」の3分類で読みます。-101だけを返すと決めつけないでください。

2.1 戻り値は負・0・正の3分類で読む

【結論】戻り値が負なら呼び出し元が前、0なら同じ順序、正なら呼び出し元が後ろです。

戻り値意味ソート時の考え方
a.compareTo(b)負の整数abより前aを先に置く
a.compareTo(b)0abは同じ順序並び順として同等
a.compareTo(b)正の整数abより後bを先に置く

判定コードでは、具体的な戻り値ではなく符号を見ます。

int result = "apple".compareTo("banana");

if (result < 0) {
    System.out.println("appleはbananaより前です");
} else if (result == 0) {
    System.out.println("同じ順序です");
} else {
    System.out.println("appleはbananaより後です");
}

2.2 StringのcompareToは辞書式に比較する

【結論】String.compareToは文字列を辞書式に比較します。大文字小文字、Unicode値、文字数の違いにより、初心者の予想と違う順序になることがあります。

System.out.println("apple".compareTo("banana")); // 負の整数
System.out.println("java".compareTo("java"));    // 0
System.out.println("zoo".compareTo("apple"));    // 正の整数

大文字小文字も区別されます。

System.out.println("Java".compareTo("java")); // 負の整数

人名や商品名の自然な並び順は、言語やロケールにより異なることがあります。単純なString.compareToで十分か、Collatorなどを検討すべきかは要件で判断します。

2.3 IntegerやLocalDateのcompareToも使える

【結論】IntegerLongLocalDateなど、多くの標準クラスはComparableを実装しています。数値や日付は自分で比較処理を書かず、標準APIを使うと読みやすくなります。

Integer left = 10;
Integer right = 20;

System.out.println(left.compareTo(right)); // 負の整数
import java.time.LocalDate;

LocalDate start = LocalDate.of(2026, 6, 1);
LocalDate end = LocalDate.of(2026, 6, 30);

if (start.compareTo(end) < 0) {
    System.out.println("startはendより前の日付です");
}

日付比較では、isBeforeisEqualisAfterのほうが意図を読みやすい場合もあります。ソートや汎用比較ではcompareTo、条件分岐では専用メソッドという使い分けが実務的です。

3. Comparableを実装して独自クラスを並べる

【結論】独自クラスに標準の並び順を持たせるなら、Comparable<T>を実装してcompareToをオーバーライドします。年齢順、ID順、名前順など、クラスの自然順序として説明できる基準を1つ選びます。

3.1 基本形はimplements Comparable

【結論】独自クラスでは、クラス宣言にimplements Comparable<クラス名>を書き、compareToを実装します。

public class Employee implements Comparable<Employee> {
    private final int id;
    private final String name;

    public Employee(int id, String name) {
        this.id = id;
        this.name = name;
    }

    public int id() {
        return id;
    }

    public String name() {
        return name;
    }

    @Override
    public int compareTo(Employee other) {
        return Integer.compare(this.id, other.id);
    }

    @Override
    public String toString() {
        return id + ":" + name;
    }
}

使い方です。

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

List<Employee> employees = new ArrayList<>();
employees.add(new Employee(3, "Sato"));
employees.add(new Employee(1, "Aoki"));
employees.add(new Employee(2, "Tanaka"));

Collections.sort(employees);
System.out.println(employees); // [1:Aoki, 2:Tanaka, 3:Sato]

3.2 引き算で比較しない

【結論】整数比較でthis.id - other.idのような引き算を使うと、オーバーフローで順序が壊れることがあります。Integer.compareを使ってください。

避けたい例です。

@Override
public int compareTo(Employee other) {
    return this.id - other.id; // オーバーフローの可能性がある
}

安全な例です。

@Override
public int compareTo(Employee other) {
    return Integer.compare(this.id, other.id);
}

longならLong.comparedoubleならDouble.compareを使います。浮動小数点数はNaN-0.0の扱いもあるため、自前の大小比較より標準メソッドが安全です。

3.3 複数条件の比較は順番を明確にする

【結論】複数条件で並べる場合は、最初の条件が同じときだけ次の条件を見るようにします。比較条件の優先順位をコードで読める形にしてください。

public class Student implements Comparable<Student> {
    private final int grade;
    private final String name;

    public Student(int grade, String name) {
        this.grade = grade;
        this.name = name;
    }

    @Override
    public int compareTo(Student other) {
        int byGrade = Integer.compare(this.grade, other.grade);
        if (byGrade != 0) {
            return byGrade;
        }
        return this.name.compareTo(other.name);
    }
}

この例は、学年の昇順、同じ学年なら名前順です。自然順序として長く使う基準ならComparableに入れてよいですが、画面や帳票ごとに変わる並び順ならComparatorを使うほうが保守しやすくなります。

4. Comparatorとの違いと使い分け

【結論】Comparableはクラス自身の標準順序、Comparatorは呼び出し側が指定する並び順です。1つの自然順序だけでは足りない場合は、Comparatorを優先します。

4.1 Comparableは自然順序をクラスに持たせる

【結論】Comparableは、そのクラスの代表的な並び順を定義します。ID順や日付順のように、どこで使っても自然だと説明できる順序に向いています。

Comparableを実装すると、Collections.sort(list)のように比較条件を明示しなくても並び替えられます。一方で、クラスに順序の責務が入るため、あとから別の順序を追加したい場合には向きません。

4.2 Comparatorは外部から並び順を渡す

【結論】Comparatorは、画面や処理ごとに異なる並び順を指定するために使います。Java 8以降はComparator.comparingで読みやすく書けます。

import java.util.Comparator;
import java.util.List;

List<Employee> employees = List.of(
    new Employee(3, "Sato"),
    new Employee(1, "Aoki"),
    new Employee(2, "Tanaka")
);

employees.stream()
    .sorted(Comparator.comparing(Employee::name))
    .forEach(System.out::println);

Comparatorの仕様やcomparingthenComparingnullsFirstなどのメソッドはComparator Java API Documentationで確認できます。

4.3 判断基準を表で整理する

【結論】自然順序が1つならComparable、並び替え条件が複数あるならComparatorを使います。

判断ポイントComparableComparator
順序を置く場所クラス自身呼び出し側
並び順の数基本的に1つ複数作れる
代表例ID順、日付順、自然な名前順名前降順、部署順、画面専用ソート
呼び出し例Collections.sort(list)list.sort(comparator)
実務での使いどころドメイン上の自然順序UI、検索結果、帳票、テスト

迷ったときは、クラスのコメントに「この型の自然順序は何か」を一文で書けるか確認します。書けない場合はComparatorのほうが適切です。

5. ソートでcompareToを使う手順

【結論】compareToを使ったソートは、型がComparableを実装しているか確認し、自然順序でよいか判断し、必要ならComparatorへ切り替える流れで進めます。

5.1 標準クラスはそのままsortできる

【結論】StringIntegerなどの標準クラスは、自然順序が定義されているためそのまま並べ替えられます。

import java.util.Arrays;

String[] words = {"orange", "apple", "banana"};
Arrays.sort(words);

System.out.println(Arrays.toString(words)); // [apple, banana, orange]

配列ならArrays.sort、リストならCollections.sortまたはList.sortを使います。

import java.util.ArrayList;
import java.util.List;

List<Integer> scores = new ArrayList<>(List.of(80, 100, 60));
scores.sort(null); // 自然順序

System.out.println(scores); // [60, 80, 100]

5.2 独自クラスはComparableかComparatorが必要

【結論】独自クラスを自然順序で並べたいならComparable、一時的な条件で並べたいならComparatorを用意します。どちらもない状態で自然順序ソートをしようとすると失敗します。

失敗しやすい例です。

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

class Task {
    final int priority;

    Task(int priority) {
        this.priority = priority;
    }
}

List<Task> tasks = new ArrayList<>();
tasks.add(new Task(2));
tasks.add(new Task(1));

Collections.sort(tasks); // コンパイルエラー

Collections.sortは、要素がComparableとして比較できることを要求します。Taskに自然順序を持たせるか、tasks.sort(Comparator.comparingInt(task -> task.priority))のように外部の比較条件を渡します。

5.3 JUnitで比較規約を確認する

【結論】compareToの実装は、単発の動作確認だけではなく、境界値と同値の扱いをテストしてください。比較規約の破綻はソート後に発見されることが多いです。

JUnit 5の簡単なテスト例です。

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;

import org.junit.jupiter.api.Test;

class EmployeeTest {
    @Test
    void compareToSortsById() {
        Employee first = new Employee(1, "Aoki");
        Employee second = new Employee(2, "Sato");

        assertTrue(first.compareTo(second) < 0);
        assertTrue(second.compareTo(first) > 0);
        assertEquals(0, first.compareTo(new Employee(1, "Aoki")));
    }
}

MavenやGradleでJUnitを使う場合は、プロジェクトにJUnit Jupiterの依存関係を追加します。compareTo自体はJava標準APIなので、追加ライブラリなしで使えます。

6. よくあるエラーと対処法

【結論】compareToのトラブルは、null、型不一致、比較規約違反、戻り値の誤読に集中します。エラーメッセージと比較対象の型を先に確認してください。

6.1 NullPointerExceptionが出る

【結論】比較対象または比較に使うフィールドがnullのままcompareToを呼ぶと、NullPointerExceptionが発生します。

String name = null;
System.out.println(name.compareTo("Aoki")); // NullPointerException

フィールド比較でも同じです。

public int compareTo(Student other) {
    return this.name.compareTo(other.name); // nameがnullなら失敗する
}

対処手順です。

  • nullを許さない設計なら、コンストラクタでObjects.requireNonNullを使う
  • nullを許す設計なら、並び順でnullを先頭に置くか末尾に置くか決める
  • Comparator.nullsFirstまたはComparator.nullsLastを使う
  • 例外が出た行の呼び出し元と引数を確認する
import java.util.Comparator;

Comparator<Student> byName =
    Comparator.comparing(Student::name, Comparator.nullsLast(String::compareTo));

6.2 ClassCastExceptionやコンパイルエラーが出る

【結論】比較できない型を自然順序でソートしようとすると、コンパイルエラーやClassCastExceptionの原因になります。ジェネリクスで型を明確にしてください。

古いコードでraw typeを使うと、実行時まで問題が残ることがあります。

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

List values = new ArrayList();
values.add("java");
values.add(10);

Collections.sort(values); // 実行時にClassCastExceptionの可能性

修正例です。

List<String> values = new ArrayList<>();
values.add("java");
values.add("spring");

Collections.sort(values);

型を混在させる必要がある設計は、比較基準が曖昧になりやすいです。まず同じ型のリストに分けられないか検討してください。

6.3 IllegalArgumentException: Comparison method violates its general contract

【結論】比較処理が一貫した順序を返さない場合、ソート中にIllegalArgumentExceptionが出ることがあります。比較結果が推移的で、同じ入力に対して同じ結果を返すか確認してください。

代表的な原因です。

  • ランダム値や現在時刻を使って比較している
  • a < bb < cなのにa > cになる比較をしている
  • 比較対象のフィールドをソート中に変更している
  • nullや特殊値の扱いが分岐ごとに揺れている

比較処理では、同じ2つの値を渡したら常に同じ符号を返すことが重要です。

6.4 戻り値を1と-1だけで判定してしまう

【結論】compareToの戻り値は任意の負の整数または正の整数です。result == -1result == 1のような判定は避けます。

避けたい例です。

int result = "apple".compareTo("banana");

if (result == -1) {
    System.out.println("前に並びます");
}

正しい例です。

int result = "apple".compareTo("banana");

if (result < 0) {
    System.out.println("前に並びます");
}

String.compareToは文字の差を返すことがあり、-11とは限りません。

7. 実務で使える実装パターン

【結論】実務では、自然順序はシンプルに保ち、画面や検索条件ごとの並び替えはComparatorへ分けると保守しやすくなります。比較ロジックはテストしやすい形にしてください。

7.1 型選びではなく比較方法で選ぶ

【結論】compareToを使うかどうかは、型そのものより「自然順序を持たせるか」で決めます。

要件推奨理由
文字列を単純な辞書順にしたいString.compareTo標準の自然順序で足りる
数値を昇順にしたいInteger.compareまたは自然順序オーバーフローを避けやすい
独自クラスに標準順序があるComparableクラスの自然順序として再利用できる
画面ごとに並び順が違うComparator条件を外部化できる
nullを含むComparator.nullsFirst/nullsLastnullの位置を明示できる

Comparableを実装したあとでも、Comparatorで別の順序を指定できます。自然順序は「デフォルト」、Comparatorは「用途別の上書き」と考えると整理しやすくなります。

7.2 Comparator.comparingで読みやすくする

【結論】複数条件のソートは、Comparator.comparingthenComparingを使うと意図が読みやすくなります。

import java.util.Comparator;
import java.util.List;

List<Employee> employees = List.of(
    new Employee(2, "Sato"),
    new Employee(1, "Sato"),
    new Employee(3, "Aoki")
);

employees.stream()
    .sorted(
        Comparator.comparing(Employee::name)
            .thenComparingInt(Employee::id)
    )
    .forEach(System.out::println);

この例は、名前順、同じ名前ならID順です。compareToにすべて詰め込むより、呼び出し側の要件が見えやすくなります。

7.3 デバッグでは比較対象と符号をログに出す

【結論】ソート結果が想定と違う場合は、比較対象の値と戻り値の符号を確認します。戻り値の具体的な数値ではなく、負・0・正の分類を見ることが重要です。

簡単な確認例です。

static int compareById(Employee left, Employee right) {
    int result = Integer.compare(left.id(), right.id());
    System.out.printf("left=%d right=%d result=%d%n", left.id(), right.id(), result);
    return result;
}

本番コードで大量ログを出すと処理が遅くなります。デバッグ用の一時コード、テスト、ブレークポイントで原因を確認してください。

8. compareTo実装前のチェックリスト

【結論】compareToは短いメソッドですが、ソート、重複判定、検索構造に影響します。実装前に比較基準と例外条件を明確にしてください。

8.1 実装前に決めること

【結論】比較基準、nullの扱い、equalsとの関係、可変フィールドの有無を先に決めると、後からの不具合を減らせます。

  • そのクラスの自然順序を一文で説明できるか
  • compareToが0のとき、equalsもtrueにする設計か
  • nullを許すか、禁止するか
  • 比較に使うフィールドがソート中に変わらないか
  • 引き算ではなく標準のcompareメソッドを使っているか
  • 複数条件の優先順位がテストで確認されているか

8.2 MavenやGradleで追加依存関係は不要

【結論】compareToComparableComparatorはJava標準APIです。JDKが使える状態なら、MavenやGradleの追加依存関係なしで利用できます。

JDKの確認コマンドです。

java -version
javac -version

JUnitでテストを書く場合だけ、プロジェクトにJUnitの依存関係を追加します。ビルドツールの設定が原因でcompareToが使えない、という問題は通常ありません。

8.3 迷ったときの最短手順

【結論】まず標準クラスの自然順序で足りるか確認し、独自クラスならComparableComparatorを選び、最後にnullと同値のテストを追加します。

  1. 比較したい値の型を確認する
  2. 標準クラスのcompareToで足りるか確認する
  3. 独自クラスなら自然順序を1つ定義できるか確認する
  4. 自然順序ならComparableを実装する
  5. 用途別の並び順ならComparatorを作る
  6. null、同じ値、最小値、最大値のテストを書く

この流れなら、compareToを使うべき場面とComparatorへ分けるべき場面を切り分けやすくなります。

9. FAQ

【結論】compareToは戻り値の符号、自然順序、Comparatorとの違いを押さえると理解しやすくなります。実装ではnull、引き算、equalsとの関係に注意してください。

9.1 JavaのcompareToとは何ですか?

compareToは、Comparableを実装したオブジェクト同士の順序を比較するメソッドです。戻り値が負なら呼び出し元が前、0なら同じ順序、正なら呼び出し元が後ろという意味になります。

9.2 compareToの戻り値は-1、0、1だけですか?

いいえ、compareToは負の整数、0、正の整数を返す契約です。-1や1だけを前提にせず、0より小さいか大きいかで判定してください。

9.3 ComparableとComparatorの違いは何ですか?

Comparableはクラス自身の自然順序を定義し、Comparatorは外部から別の並び順を渡すために使います。1つの標準的な順序ならComparable、複数の並び替え条件が必要ならComparatorが向いています。

9.4 compareToでNullPointerExceptionが出る原因は何ですか?

比較対象や比較に使うフィールドがnullのままcompareToを呼ぶと発生します。nullを許す設計なら、Comparator.nullsFirstやnullsLast、または明示的なnullチェックを使います。

9.5 compareToを実装するときに引き算で比較してよいですか?

単純な引き算はオーバーフローで順序を誤ることがあります。整数の比較にはInteger.compare、longにはLong.compare、複数条件にはComparator.comparingを使うほうが安全です。

9.6 compareToとequalsは同じ結果にする必要がありますか?

必ず同じでなければコンパイルできないわけではありませんが、同じ順序と等価性を表す設計が推奨されます。TreeSetやTreeMapではcompareToが0の要素を同じ順序として扱うため、equalsとの不一致は重複判定の誤解につながります。

9.7 StringのcompareToは大文字小文字を区別しますか?

String.compareToはUnicode値に基づく辞書式比較で、大文字小文字を区別します。大文字小文字を無視したい場合はcompareToIgnoreCaseや適切なComparatorを検討します。