Effective Javaを読んでいる⑨

equalsをオーバーライドするときはhashCodeもオーバーライドしなさい

Object#hashCode()の規約

  1. 同じオブジェクト(equalsで比較する値が変わってない)場合は、常に同じhashCodeを返す。アプリケーションを2回実行した時に1回目と2回目のhashCodeは別でもよい。
  2. x.equals(y)がtrueなら、x.hashCode()とy.hashCode()は同じである必要がある。
  3. x.equals(y)がfalseの時に、x.hashCode()とy.hashCode()が異なる必要はない。が、異なるほうがハッシュテーブルとかのパフォーマンスが上がる。
    Object (Java Platform SE 6)

規約の上ではこれはありなんだな。

public class Oshiri {
    @Override
    public int hashCode(){
        return 100;
    }
}

HashMap#get()

equalsは実装してるけどhashCodeは実装してない場合、equalsでは等しいがhashCodeの値は異なることになる。
HashMapにputしたインスタンスAと、getのキーになるインスタンスBが論理的に同じであっても、hashCodeの値が異なるとgetの結果nullがかえってくる。
nullが返却される流れ
①HashMapはputされたインスタンスのhashCodeをキャッシュする。
②getが呼び出されると、引数のインスタンスのhashCodeを見る。
③キャッシュ済のhashCodeに②のhashCodeがなかったら、nullを返却する。(ここでは、putされた値と引数が論理的に等しいかどうかはチェックしない)

Object#hashCode()のレシピ

  1. 適当なintの値を保持する。
  2. equalsに影響をもつフィールドの各値をintに変換する。
    • booleanは0か1に
    • byte,char,shortはintにキャスト(intはそのまま)
    • longは(ff>>>32) *何してんだこれ
    • floatはfloatToIntBits
    • doubleはdoubleToLongBitsからのlongの謎計算
    • オブジェクトの参照のフィルドはそいつのhashCodeを呼ぶ。それがダメならインスタンスの各要素からまた頑張って計算。nullなら0。
    • 配列は全要素に対して上記 result = 適当なintの値 + result + フィールドをintに変換したやつ ↑を全フィールドに対して計算 result = 9 + result + 身長 result = 9 + result + 体重 result = 9 + result + 年齢 みたいな
  3. resultを返却
  4. あってんのか確認、テスト

もしオブジェクトがImmutableでhashCodeの計算のコストを避けたいなら、インスタンスを作った時点でhashCodeを計算してフィールドに持ってしまえばいい 。もしくはhashCodeが初めて呼び出された時に計算して2回目以降は最初に計算した値を使う。