目的
- Javaで hashCode 関連のメソッドはいくつかのクラスに分散しているので、整理してみます。
- 各メソッドの詳細な説明はしません。
hashCode の基本
- ハッシュ値を求める。ハッシュテーブル探索などで使われる(
HashMap
やHashSet
など) -
equals()
とセットで、矛盾なく実装する必要がある-
equals()
がtrue
を返すオブジェクトは、同じハッシュ値であること - 違うハッシュ値である場合には、
equals()
はfalse
を返すこと(対偶) -
equals()
がfalse
を返すオブジェクトが、同じハッシュ値であっても構わない(ハッシュ値の衝突)が、衝突が少ないほうが性能が良い
-
メソッド
java.lang.Object#hashCode()
インスタンスのハッシュ値を計算する。
独自のクラスを HashMap
などのキーに使いたいときには自分で実装する必要がある。
java.util.Objects#hash()
可変引数で渡したオブジェクトのハッシュ値を計算します。
実際には後述の Arrays#hashCode()
を呼び出しているようなものです。
java.util.Objects#hashCode()
指定した1つのオブジェクトのハッシュ値を計算します。
null
を渡した場合にもエラーになりません。
java.util.Arrays#hashCode()
配列オブジェクトの hashCode()
は、Object
と同じ結果を返します(つまり配列の値を使わずに計算しています)。
もし、配列の内容を元にハッシュ値を計算したい場合には、Arrays#hashCode()
を使います。
null
を渡した場合にもエラーになりません。
ただし、多次元配列などは思ったとおりの計算結果にならないと思います。
必要であれば Arrays#deepHashCode()
を使ってください。
また、ハッシュ計算の対象が配列の一部だけといったケースがあれば(たとえば、容量だけ確保してデータはまだ入れていない状態など)、自力で計算してください。
java.util.Arrays#deepHashCode()
ネストした配列のハッシュ値を求めます。
自己循環参照した配列は渡さないほうが良いです(無限ループやスタックオーバーフローが起きるかもしれません)。
null
を渡した場合にもエラーになりません。
java.lang.Integer#hashCode()
同じ名前ですが、Integer
型のハッシュ値を求めるインスタンスメソッドと、int
のハッシュ値を求めるクラススタティックメソッドがあります。
Integer i = getInteger();
int hash1 = i.hashCode();
int hash2 = Integer.hashCode(1234);
また、Integer
以外のプリミティブ型のラッパークラスにも同様のメソッドがあります。
java.lang.System#identityHashCode()
Object
のハッシュ値と同じ計算方法で、指定したオブジェクトのハッシュ値を求めます。オブジェクトが独自のハッシュ計算をしていても大丈夫。
インスタンスの識別が(ある程度)できるように、ログ出力時や toString()
で生成する文字列に追加することがあります。
異なるインスタンスであっても同じハッシュ値になる可能性はあるので、飽くまで参考情報ですが。
計算方法(実装方法)
手動でハッシュ計算
基本的には、以下のようにハッシュ計算に使うべきフィールドの値から計算をすることが多いです。
@Override
public int hashCode()
int result = 17;
result *= 31;
result += フィールド1のハッシュ値;
result *= 31;
result += フィールド2のハッシュ値;
result *= 31;
result += フィールド3のハッシュ値;
return result;
}
またフィールドのハッシュ値の計算方法(例)は以下のような感じです。
-
int
の値はそのまま -
byte
,char
,short
の値はint
に変換して -
boolean
は真が1
、偽が0
-
long
の値は上位 32bit と下位 32bit に分割して、ビット単位の xor でint
に変換 -
float
はFloat#floatToIntBits()
でint
に変換 -
double
はDouble#doubleToLongBits()
でlong
に変換して、あとはlong
と同じ - その他のオブジェクトの場合にはオブジェクトの
hashCode()
を呼び出して(あるいは実装されていない場合には、同じようにがんばる)
このあたりは有名な「Effective Java 2nd Edition」に詳しいです。
親クラスの hashCode()
親クラスのフィールドの値がすべて取れるのであれば、子クラス側ですべて計算してもいいですが、普通は親クラスの hashCode()
を呼び出してその分を考慮することになると思います。
ただし、親クラスが hashCode()
を実装していない(Objectクラスの hashCode()のままだった)場合には、親クラスの hashCode()
の結果を使ってはいけないです。
Java7 のユーティリティメソッドを活用
Java7 からは便利なメソッドが追加されているので、たいていの場合には簡単な記述でハッシュ値を計算できるようになっています。
@Override
public int hashCode()
return Objects.hash(フィールド1,フィールド2,フィールド3);
}
IDE で自動生成
たとえば Eclipse ではメニューから "Source" → "Generate hashCode() and equals() ..." を選べば、自動生成することができます。
Lombokでハッシュ計算
@Data
アノテーション、@Value
アノテーション、あるいは @EqualsAndHashCode
アノテーションを付ければ OK?
その他
URL の hashCode()
こんな話があるみたいです。
URL
の hashCode()
を求める際に、ホスト名のアドレス解決をするので、環境やタイミングによりハッシュ値が変わる可能性があるとのこと。
URL
ではなく、URI
で判定すべきなんでしょうね。
String の hashCode()
こちらは昔話のようですが。
Java の途中のバージョンで、ハッシュ計算方法の見直しがあったという話。
標準のハッシュ値の計算方法
標準(java.lang.Object
)のハッシュ値の計算方法は、VM 起動時に変更が可能なようです。
また Java7 までと Java8 ではデフォルト値が変わっているとのこと。
ためしに Windows 上で以下のコマンドを実行してみたところ。
c:\> java -XX:+PrintFlagsFinal -version
(中略)
intx hashCode = 5 {product}
java version "1.8.0_65"
Java(TM) SE Runtime Environment (build 1.8.0_65-b17)
Java HotSpot(TM) Client VM (build 25.65-b01, mixed mode)
確かに、hashCode は 5 になっています。
同様に、JDK1.7 で確認すると hashCode は 0 になっています。