0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Effective Java 3-14:Comparableの実装を検討する

Posted at

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 を用意し、必要ならそれらをドキュメント化する。
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?