Javaメモ: equals をオーバライドする時は常に hashCode をオーバライドする

equals を実装したら、hashCode も必ず実装する

equals(Object) メソッドをオーバライドした場合は、必ず hashCode() メソッドもオーバライドして正しく実装する必要があります。 hashCode() を正しくオーバライドしないと、ハッシュ構造を用いたコンテナ(HashSet, HashMap など)に対してそのオブジェクトを格納した場合に、正常に動作しなくなってしまいます。

hashCode 契約

hashCode は、下記の契約に基づいて実装されなければいけません。

  1. アプリケーションのライフサイクル内(単一の Virtual Machine 内)において、hashCode() の返す値は、何度呼び出しても同じ値を返す必要がある(ただし、equals(Object) において同値とみなされなくなるようなフィールドの変更があった場合は、hashCode() の返す値は変化してよい)。
  2. equals(Object) によって同値とみなされるオブジェクト同士の hashCode() は同じでなければならない。
  • hashCode() は、デフォルトではインスタンスごとに異なる値を返そうとするため、equals(Object) を論理的な同値を判断するようにオーバライドしたら、hashCode() も論理的に同じオブジェクトの場合には同じ値を返すように実装しなければいけません。
  1. equals(Object) の結果が false になるオブジェクト同士の hashCode() は異なる値を返すのが望ましい
  • 同値とみなされない 2 つのオブジェクトが、同じハッシュ値を返すように実装しても、動作上は問題ありませんが、パフォーマンス上不利になることがあります。
  • 異なるハッシュ値を返すケースでは、それらの値は、ハッシュ値の取りうる範囲において適度に分散されていると、よりよいパフォーマンスを望めることがあります。

hashCode 実装の処方箋

Effective Java に、適度に理想的な hashCode 実装を行うための指針が示されており、Android の Object クラスの説明では、この指針に従ったサンプルコードが掲載されています。

@Override public int hashCode() {
    // Start with a non-zero constant.
    int result = 17;

    // Include a hash for each field.
    result = 31 * result + (booleanField ? 1 : 0);

    result = 31 * result + byteField;
    result = 31 * result + charField;
    result = 31 * result + shortField;
    result = 31 * result + intField;

    result = 31 * result + (int) (longField ^ (longField >>> 32));

    result = 31 * result + Float.floatToIntBits(floatField);

    long doubleFieldBits = Double.doubleToLongBits(doubleField);
    result = 31 * result + (int) (doubleFieldBits ^ (doubleFieldBits >>> 32));

    result = 31 * result + Arrays.hashCode(arrayField);

    result = 31 * result + referenceField.hashCode();
    result = 31 * result + (nullableReferenceField == null ? 0 : nullableReferenceField.hashCode());

    return result;
}

上記のコード内の xxxxField という変数は、同値判定のために参照されるフィールドを示しています。 変数のプレフィックス (xxxx) は、そのフィールドの型を示しており、それぞれの型の値をどのようにハッシュ値の計算のために使用すべきかが示されています。

参考