[Java] hashCode 方法的记事本

要搞清楚要达到的目标

    • Javaで 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类型的哈希值。

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第2版》非常了解。

父类的 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注解,可以吗?

其他

URL 的哈希码()

好像有这样的故事。

    java.net.URLの闇

在计算URL的hashCode()时,需要对主机名进行地址解析,因此根据环境和时机可能会导致哈希值发生变化。
也许应该通过URI而不是URL来进行判断吧。

字符串的 hashCode()

这似乎是一个古老的故事。

    String.hashCode() の変遷

在Java的某个中间版本中,有关重新评估哈希计算方法的讨论。

计算标准哈希值的方法

在中国的本地语言中进行同义转述:标准(java.lang.Object)哈希值的计算方法似乎可以在虚拟机启动时进行更改。另外,据说在Java7及之前和Java8中,默认值有所不同。

    Java8 で java.lang.Object#hashCode() の生成アルゴリズムが変更されていました。

我在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。