3-14:Comparableの実装を検討する
要点
クラスに「自然な順序(natural ordering)」があるなら Comparable を実装しておくと便利。ただし compareTo の契約(符号の反転性・推移性・一貫性・null 取扱等)を守り、equals との整合性やオーバーフローなどの落とし穴に注意する。
いつ Comparable を実装するか
-
「この型のオブジェクトを並べ替えるとき、誰もが期待する(常識的な)順序が存在する」→ 実装する価値あり。
例:文字列は辞書順、数値ラッパーは数値順、日付は旧→新、など。 -
逆に「並べ替え方が用途によって複数ある」なら Comparator を用意する方が柔軟(複数の順序を簡単に切り替えられる)。
compareTo の契約
1. 反対称性(sign flip): sgn(x.compareTo(y)) == -sgn(y.compareTo(x))。
2. 推移性: もし x.compareTo(y) > 0 かつ y.compareTo(z) > 0 なら x.compareTo(z) > 0。
3. 一貫性: 同じオブジェクト状態なら何度呼んでも同じ結果。
4. null: x.compareTo(null) は一般に NullPointerException を投げる(JDK の慣習)。
5. equals との整合: 可能なら compareTo(x,y)==0 ⇔ x.equals(y) にする(特に TreeSet/TreeMap に入れる可能性があるなら重要)。整合していないと、ソート済みコレクションで期待外れの動作(重複判定の不一致)が起きる。
悪い例
1. 引き算で比較(オーバーフローの危険)
// NG (int の差を返すのはオーバーフローの恐れあり)
@Override
public int compareTo(Person other) {
return this.age - other.age;
}
→ age が大きく異なるとオーバーフローして符号が逆転することがある。
2. compareTo と equals が不整合
- compareTo が 0 を返すが equals は false → TreeSet に入れると<一方が既にあると見なされる・取り出せない>等の不具合。
良い例
単純フィールド(int / double)
@Override
public int compareTo(Person other) {
return Integer.compare(this.age, other.age);
}
// double の場合は Double.compare
複数フィールドで順序付け
@Override
public int compareTo(Person other) {
int cmp = Integer.compare(this.age, other.age);
if (cmp != 0) return cmp;
return this.name.compareTo(other.name);
}
または Java 8+ なら Comparator を使って compareTo を実装:
private static final Comparator<Person> NATURAL =
Comparator.comparingInt(Person::getAge).thenComparing(Person::getName);
@Override
public int compareTo(Person other) {
return NATURAL.compare(this, other);
}
Comparator を併用する理由
-
複数の並べ方が必要なとき、Comparator を作るべき(Comparator.comparing / thenComparing / reversed / nullsFirst 等が便利)。
-
クラス自体は Comparable を提供せず、ライブラリ側で Comparators を供給する設計もある。
Sorted コレクションでの注意点(実務で痛い失敗)
-
TreeSet / TreeMap は順序(compareTo/Comparator)に基づいて同一性を判定する。
→ compareTo(a,b) == 0 のとき 片方しか保持されない(Set では重複とみなされる)。 -
つまり compareTo が 0 を返すケースを慎重に設計する(ID 等で同一扱いしたくなければ equals と整合させる)。
まとめ
- 自然な順序が 明確に存在する クラスは Comparable を実装すると便利。
- 実装は Integer.compare / Double.compare / Comparator ヘルパーを使って安全に書く。
- compareTo と equals の整合性を意識し、意図しない「0 の同一扱い」で Sorted コレクションが壊れないようにする。
- 複数の並べ方が要るなら Comparator を用意し、必要ならそれらをドキュメント化する。