[Java] hashCode のメモ


# 目的

- 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()

指定した１つのオブジェクトのハッシュ値を計算します。
`null` を渡した場合にもエラーになりません。

## java.util.Arrays#hashCode()

配列オブジェクトの `hashCode()` は、`Object` と同じ結果を返します（つまり配列の値を使わずに計算しています）。

もし、配列の内容を元にハッシュ値を計算したい場合には、`Arrays#hashCode()` を使います。
`null` を渡した場合にもエラーになりません。
ただし、多次元配列などは思ったとおりの計算結果にならないと思います。
必要であれば `Arrays#deepHashCode()` を使ってください。

また、ハッシュ計算の対象が配列の一部だけといったケースがあれば（たとえば、容量だけ確保してデータはまだ入れていない状態など）、自力で計算してください。

## java.util.Arrays#deepHashCode()

ネストした配列のハッシュ値を求めます。
自己循環参照した配列は渡さないほうが良いです（無限ループやスタックオーバーフローが起きるかもしれません）。
`null` を渡した場合にもエラーになりません。

## java.lang.Integer#hashCode()

同じ名前ですが、`Integer` 型のハッシュ値を求めるインスタンスメソッドと、`int` のハッシュ値を求めるクラススタティックメソッドがあります。

```java
Integer i = getInteger();
int hash1 = i.hashCode();

int hash2 = Integer.hashCode(1234);
```

また、`Integer` 以外のプリミティブ型のラッパークラスにも同様のメソッドがあります。

## java.lang.System#identityHashCode()

`Object` のハッシュ値と同じ計算方法で、指定したオブジェクトのハッシュ値を求めます。オブジェクトが独自のハッシュ計算をしていても大丈夫。

インスタンスの識別が（ある程度）できるように、ログ出力時や `toString()` で生成する文字列に追加することがあります。
異なるインスタンスであっても同じハッシュ値になる可能性はあるので、飽くまで参考情報ですが。

# 計算方法（実装方法）

## 手動でハッシュ計算

基本的には、以下のようにハッシュ計算に使うべきフィールドの値から計算をすることが多いです。

```java
@Override
public int hashCode()
    int result = 17;

    result *= 31;
    result += フィールド１のハッシュ値;

    result *= 31;
    result += フィールド２のハッシュ値;

    result *= 31;
    result += フィールド３のハッシュ値;

    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 からは便利なメソッドが追加されているので、たいていの場合には簡単な記述でハッシュ値を計算できるようになっています。

```java
@Override
public int hashCode()
    return Objects.hash(フィールド１,フィールド２,フィールド３);
}
```

## IDE で自動生成

たとえば Eclipse ではメニューから "Source" → "Generate hashCode() and equals() ..." を選べば、自動生成することができます。

## Lombokでハッシュ計算

`@Data` アノテーション、`@Value` アノテーション、あるいは `@EqualsAndHashCode` アノテーションを付ければ OK？

# その他

## URL の hashCode()

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

- [java.net.URLの闇](http://blog.scheakur.com/post/74938746551/javaneturl%E3%81%AE%E9%97%87)

`URL` の `hashCode()` を求める際に、ホスト名のアドレス解決をするので、環境やタイミングによりハッシュ値が変わる可能性があるとのこと。
`URL` ではなく、`URI` で判定すべきなんでしょうね。

## String の hashCode()

こちらは昔話のようですが。

- [String.hashCode() の変遷](http://d.hatena.ne.jp/fd0/20080510/p1)

Java の途中のバージョンで、ハッシュ計算方法の見直しがあったという話。

## 標準のハッシュ値の計算方法

標準(`java.lang.Object`)のハッシュ値の計算方法は、VM 起動時に変更が可能なようです。
また Java7 までと Java8 ではデフォルト値が変わっているとのこと。

- [Java8 で java.lang.Object#hashCode() の生成アルゴリズムが変更されていました。](http://d.hatena.ne.jp/chiheisen/20140706/1405767267)

ためしに Windows 上で以下のコマンドを実行してみたところ。

```js
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 になっています。