hashCode() 方法

当 Java 类重写 equals 方法时,它也应该覆盖 hashCode 方法。如方法合同中所定义 :

  • 每当在执行 Java 应用程序期间多次在同一对象上调用它时,hashCode 方法必须始终返回相同的整数,前提是不修改在对象的等比较中使用的信息。从应用程序的一次执行到同一应用程序的另一次执行,该整数不需要保持一致。
  • 如果两个对象根据 equals(Object) 方法相等,则在两个对象中的每一个上调用 hashCode 方法必须产生相同的整数结果。
  • 根据 equals(Object) 方法,如果两个对象不相等则不需要,则在两个对象中的每一个上调用 hashCode 方法必须产生不同的整数结果。但是,程序员应该知道为不等对象生成不同的整数结果可能会提高哈希表的性能。

哈希代码用于哈希实现,例如 HashMapHashTableHashSethashCode 函数的结果决定了放置对象的存储桶。如果提供的 hashCode 实现很好,这些哈希实现会更有效。良好的实施的一个重要特性是 hashCode 值的分布是均匀的。换句话说,很多实例存储在同一个存储桶中的可能性很小。

用于计算哈希码值的算法可以类似于以下内容:

public class Foo {
    private int field1, field2;
    private String field3;

    public Foo(int field1, int field2, String field3) {
        this.field1 = field1;
        this.field2 = field2;
        this.field3 = field3;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (obj == null || getClass() != obj.getClass()) {
            return false;
        }

        Foo f = (Foo) obj;
        return field1 == f.field1 &&
               field2 == f.field2 &&
               (field3 == null ? f.field3 == null : field3.equals(f.field3);
    }

    @Override
    public int hashCode() {
        int hash = 1;
        hash = 31 * hash + field1;
        hash = 31 * hash + field2;
        hash = 31 * hash + (field3 == null ? 0 : field3.hashCode());
        return hash;
    }
}

使用 Arrays.hashCode() 作为捷径

Version >= Java SE 1.2

在 Java 1.2 及更高版本中,不是开发计算哈希码的算法,而是可以使用 java.util.Arrays#hashCode 通过提供包含字段值的 Object 或 primitives 数组来生成:

@Override
public int hashCode() {
    return Arrays.hashCode(new Object[] {field1, field2, field3});
}

Version >= Java SE 7

Java 1.7 引入了 java.util.Objects 类,它提供了一种方便的方法 hash(Object... objects),它根据提供给它的对象的值计算哈希码。这种方法就像 java.util.Arrays#hashCode 一样。

@Override
public int hashCode() {
    return Objects.hash(field1, field2, field3);
}

注意:这种方法效率很低,每次调用自定义 hashCode() 方法时都会生成垃圾对象:

  • 创建临时 Object[]。 (在 Objects.hash() 版本中,数组由 varargs 机制创建。)
  • 如果任何字段是基本类型,则必须将它们装箱并且可以创建更多临时对象。
  • 必须填充该数组。
  • 数组必须通过 Arrays.hashCodeObjects.hash 方法迭代。
  • Object.hashCode() 或者 Objects.hash 必须进行的调用(可能)不能内联。

哈希码的内部缓存

由于对象的哈希码的计算可能很昂贵,因此在第一次计算对象时将哈希码值缓存在对象中会很有吸引力。例如

public final class ImmutableArray {
    private int[] array;
    private volatile int hash = 0;

    public ImmutableArray(int[] initial) {
        array = initial.clone();
    }

    // Other methods

    @Override
    public boolean equals(Object obj) {
         // ...
    }

    @Override
    public int hashCode() {
        int h = hash;
        if (h == 0) {
            h = Arrays.hashCode(array);
            hash = h;
        }
        return h;
    }
}

这种方法折算(重复)计算哈希码的成本与额外字段的开销以缓存哈希码。这种性能优化是否会得到回报将取决于给定对象被散列(查找)的频率和其他因素。

你还会注意到,如果 ImmutableArray 的真实哈希码恰好为零(2 32 中 有一次机会 ),则缓存无效。

最后,如果我们正在散列的对象是可变的,那么这种方法更难以正确实现。但是,如果哈希码发生变化,则存在更大的问题; 见上面的合同。