1. JavaのcompareToで何を比較するのか
【結論】compareToは、2つのオブジェクトの「並び順」を決めるためのメソッドです。等しいかどうかだけでなく、前に置くか、同じ順序か、後ろに置くかを整数の符号で表します。
1.1 compareToは順序を返すメソッド
【結論】compareToはComparable<T>インターフェースに定義されているメソッドです。戻り値の符号を使って自然順序を表します。
【定義】自然順序とは、そのクラスが標準として持つ並び順です。Stringなら辞書式順序、Integerなら数値順のように、クラス自身が自然だと定義する順序を指します。
Comparable<T>の基本形は次のとおりです。
public interface Comparable<T> {
int compareTo(T other);
}
OracleのAPI仕様では、ComparableのcompareToがクラスの自然順序を表すと説明されています。仕様を確認したい場合はComparable Java API Documentationを参照してください。
1.2 compareToはソート処理の土台になる
【結論】compareToは、Collections.sort、List.sort、Arrays.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 | ソート、TreeSet、TreeMap |
TreeSetやTreeMapは順序に基づいて要素を扱います。compareToが0を返すのにequalsがfalseになる設計では、重複していないと思った要素が追加されないなど、初心者には分かりにくい挙動になります。
2. compareToの戻り値を正しく読む
【結論】compareToの戻り値は「負の整数」「0」「正の整数」の3分類で読みます。-1、0、1だけを返すと決めつけないでください。
2.1 戻り値は負・0・正の3分類で読む
【結論】戻り値が負なら呼び出し元が前、0なら同じ順序、正なら呼び出し元が後ろです。
| 式 | 戻り値 | 意味 | ソート時の考え方 |
|---|---|---|---|
a.compareTo(b) | 負の整数 | aはbより前 | aを先に置く |
a.compareTo(b) | 0 | aとbは同じ順序 | 並び順として同等 |
a.compareTo(b) | 正の整数 | aはbより後 | 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も使える
【結論】Integer、Long、LocalDateなど、多くの標準クラスは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より前の日付です");
}
日付比較では、isBefore、isEqual、isAfterのほうが意図を読みやすい場合もあります。ソートや汎用比較では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.compare、doubleなら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の仕様やcomparing、thenComparing、nullsFirstなどのメソッドはComparator Java API Documentationで確認できます。
4.3 判断基準を表で整理する
【結論】自然順序が1つならComparable、並び替え条件が複数あるならComparatorを使います。
| 判断ポイント | Comparable | Comparator |
|---|---|---|
| 順序を置く場所 | クラス自身 | 呼び出し側 |
| 並び順の数 | 基本的に1つ | 複数作れる |
| 代表例 | ID順、日付順、自然な名前順 | 名前降順、部署順、画面専用ソート |
| 呼び出し例 | Collections.sort(list) | list.sort(comparator) |
| 実務での使いどころ | ドメイン上の自然順序 | UI、検索結果、帳票、テスト |
迷ったときは、クラスのコメントに「この型の自然順序は何か」を一文で書けるか確認します。書けない場合はComparatorのほうが適切です。
5. ソートでcompareToを使う手順
【結論】compareToを使ったソートは、型がComparableを実装しているか確認し、自然順序でよいか判断し、必要ならComparatorへ切り替える流れで進めます。
5.1 標準クラスはそのままsortできる
【結論】StringやIntegerなどの標準クラスは、自然順序が定義されているためそのまま並べ替えられます。
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 < b、b < cなのにa > cになる比較をしている- 比較対象のフィールドをソート中に変更している
- nullや特殊値の扱いが分岐ごとに揺れている
比較処理では、同じ2つの値を渡したら常に同じ符号を返すことが重要です。
6.4 戻り値を1と-1だけで判定してしまう
【結論】compareToの戻り値は任意の負の整数または正の整数です。result == -1やresult == 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は文字の差を返すことがあり、-1や1とは限りません。
7. 実務で使える実装パターン
【結論】実務では、自然順序はシンプルに保ち、画面や検索条件ごとの並び替えはComparatorへ分けると保守しやすくなります。比較ロジックはテストしやすい形にしてください。
7.1 型選びではなく比較方法で選ぶ
【結論】compareToを使うかどうかは、型そのものより「自然順序を持たせるか」で決めます。
| 要件 | 推奨 | 理由 |
|---|---|---|
| 文字列を単純な辞書順にしたい | String.compareTo | 標準の自然順序で足りる |
| 数値を昇順にしたい | Integer.compareまたは自然順序 | オーバーフローを避けやすい |
| 独自クラスに標準順序がある | Comparable | クラスの自然順序として再利用できる |
| 画面ごとに並び順が違う | Comparator | 条件を外部化できる |
| nullを含む | Comparator.nullsFirst/nullsLast | nullの位置を明示できる |
Comparableを実装したあとでも、Comparatorで別の順序を指定できます。自然順序は「デフォルト」、Comparatorは「用途別の上書き」と考えると整理しやすくなります。
7.2 Comparator.comparingで読みやすくする
【結論】複数条件のソートは、Comparator.comparingとthenComparingを使うと意図が読みやすくなります。
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で追加依存関係は不要
【結論】compareTo、Comparable、ComparatorはJava標準APIです。JDKが使える状態なら、MavenやGradleの追加依存関係なしで利用できます。
JDKの確認コマンドです。
java -version
javac -version
JUnitでテストを書く場合だけ、プロジェクトにJUnitの依存関係を追加します。ビルドツールの設定が原因でcompareToが使えない、という問題は通常ありません。
8.3 迷ったときの最短手順
【結論】まず標準クラスの自然順序で足りるか確認し、独自クラスならComparableかComparatorを選び、最後にnullと同値のテストを追加します。
- 比較したい値の型を確認する
- 標準クラスの
compareToで足りるか確認する - 独自クラスなら自然順序を1つ定義できるか確認する
- 自然順序なら
Comparableを実装する - 用途別の並び順なら
Comparatorを作る - 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を検討します。


