LoginSignup
43
45

[Java] hashCode のメモ

Last updated at Posted at 2015-11-01

目的

  • Javaで hashCode 関連のメソッドはいくつかのクラスに分散しているので、整理してみます。
  • 各メソッドの詳細な説明はしません。

hashCode の基本

  • ハッシュ値を求める。ハッシュテーブル探索などで使われる(HashMapHashSet など)
  • 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 に変換
  • floatFloat#floatToIntBits()int に変換
  • doubleDouble#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()

こんな話があるみたいです。

URLhashCode() を求める際に、ホスト名のアドレス解決をするので、環境やタイミングによりハッシュ値が変わる可能性があるとのこと。
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 になっています。

43
45
1

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
43
45