まくまくJavaノート
equals をオーバーライドする時は一般契約に従う
2016-04-22

equals をオーバライドすべきケース

equals メソッドを問題なくオーバライドすることは簡単ではありません。 問題の発生を防ぐためには equals メソッドをオーバライドしないのが一番です。 この場合は、equals は自分自身と同じオブジェクトである場合にのみ true を返すように振る舞います。

Object#equals(Object) の実装

public boolean equals(Object o) {
    return this == o;
}

振る舞いを定義するようなクラスでは、上記のようなデフォルトの equals 実装で十分なことがほとんどです。 一方で、Integer や Date クラスのような論理的な値を表すクラスでは、論理的な同値判断を行えることを期待するでしょう。 equals メソッドをオーバライドする場合は、下記のような条件を満たすことが求められます(Object クラスの説明抜粋)。

  • It is reflexive: for any non-null reference value x, x.equals(x) should return true.
  • It is symmetric: for any non-null reference values x and y, x.equals(y) should return true if and only if y.equals(x) returns true.
  • It is transitive: for any non-null reference values x, y, and z, if x.equals(y) returns true and y.equals(z) returns true, then x.equals(z) should return true.
  • It is consistent: for any non-null reference values x and y, multiple invocations of x.equals(y) consistently return true or consistently return false, provided no information used in equals comparisons on the objects is modified.
  • For any non-null reference value x, x.equals(null) should return false.

equals をオーバライドするときのチェックポイント

1. Object を引数にとる equals をオーバライドする

@Override public boolean equals(Object o) {
    ...
}

自分自身のクラスの型をパラメータにとる equals メソッドを定義(オーバロード)することは考えないほうがよいです。 結局は Object を引数にとる equals の定義も必要であり、これらの整合性をとりながら更新することは容易ではありません。 @Override アノテーションを付けて、親クラスの equals をオーバライドしていることを明示しましょう。

2. 自分自身と等しいかどうか比較する

この比較は必須ではありませんが、同じオブジェクト同士を比較する頻度が高い場合は、実行時のパフォーマンスの向上を見込めます。

@Override public boolean equals(Object o) {
    if (o == this) {
        return true;
    }
    ...
}

3. 論理的に等しい型のオブジェクトが渡されたかどうかを調べる

equals メソッドをオーバライドする目的は、そのクラス独自のフィールドの同値性を確認したいからなので、まずは渡されたオブジェクトが論理的に等しい型のオブジェクトであることをチェックします。 多くの場合は、自分自身のクラスの型と同じであるかを調べればよいのですが、このクラスがあるインタフェースを実現するための具象クラスである場合は、等しいインタフェースを実装したオブジェクトが渡されたかどうかを調べるということもあり得ます(この場合も insntanceof で OK)。

@Override public boolean equals(Object o) {
    ...
    if (!(o instanceof DerivedClass)) {
        return false;
    }
    ...
}

なお、この instanceof はオブジェクトが null のときには false を返すように動作するため、null チェックの用途も兼ねています。 equals の実装において、明示的な null チェックを行う必要はありません(冗長です)。

4. 適切な型にキャストしてフィールドの論理的な同値チェックを行う

前のステップの instanceof によって、より具体的な型にダウンキャストできることが保証されています。

@Override public boolean equals(Object o) {
    ...
    DerivedClass d = (DerivedClass) o;
    return d.field1 == field1 && d.field2 == field2;
}

実行時のパフォーマンスを考慮して、フィールドの比較順序を決める必要があります。 値が異なる可能性の高いフィールドを優先的に比較することで、equals メソッドの処理が早く終了することを期待できます。 上記の例では、field1 の比較を field2 の比較より先に行っていますが、これは、field1 が不一致になる可能性が、field2 が不一致になる可能性よりも高いことを考慮した実装です。 単純にアルファベット順に並べているわけではないことに注意してください。

5. 忘れずに hashCode をオーバライドする

equals メソッドをオーバライドした場合は、必ず hashCode メソッドもオーバライドする必要があります。

equals をオーバーライドする時は、常に hashCode をオーバーライドする

まとめ

ここまでのチェックポイントを整理すると、コードは下記のような感じになります。

@Override public boolean equals(Object o) {
    if (o == this) {
        return true;
    }
    if (!(o instanceof DerivedClass)) {
        return false;
    }
    DerivedClass d = (DerivedClass) o;
    return d.field1 == field1 && d.field2 == field2;
}

@Override public int hashCode() {
    ...
}
2016-04-22