我们如何决定集合的hashCode()方法的最佳实现(假设equals方法已被正确重写)?


当前回答

关于8.blogspot.com,你说过

如果equals()对于两个对象返回true,那么hashCode()应该返回相同的值。如果equals()返回false,那么hashCode()应该返回不同的值

我不同意你的看法。如果两个对象具有相同的hashcode,并不意味着它们是相等的。

如果A等于B,那么A.hashcode必须等于B.hascode

but

如果A.hashcode等于B.hascode,这并不意味着A必须等于B

其他回答

当组合哈希值时,我通常使用boost c++库中使用的组合方法,即:

seed ^= hasher(v) + 0x9e3779b9 + (seed<<6) + (seed>>2);

这在确保平均分配方面做得相当好。有关这个公式如何工作的一些讨论,请参阅StackOverflow的帖子:boost::hash_combine中的魔术数字

在http://burtleburtle.net/bob/hash/doobs.html上有关于不同哈希函数的很好的讨论

只是一个快速的注释,以完成其他更详细的答案(在代码方面):

如果我考虑如何在java中创建哈希表的问题,特别是jGuru FAQ条目,我相信可以判断哈希代码的其他标准是:

同步(算法是否支持并发访问)? 失败安全迭代(算法是否检测到迭代过程中发生变化的集合) 空值(哈希码是否支持集合中的空值)

下面是另一个考虑超类逻辑的JDK 1.7+方法演示。我认为它对对象类hashCode()进行记帐非常方便,纯粹依赖于JDK,没有额外的手工工作。请注意Objects.hash()是空容忍的。

我没有包括任何equals()实现,但实际上您当然需要它。

import java.util.Objects;

public class Demo {

    public static class A {

        private final String param1;

        public A(final String param1) {
            this.param1 = param1;
        }

        @Override
        public int hashCode() {
            return Objects.hash(
                super.hashCode(),
                this.param1);
        }

    }

    public static class B extends A {

        private final String param2;
        private final String param3;

        public B(
            final String param1,
            final String param2,
            final String param3) {

            super(param1);
            this.param2 = param2;
            this.param3 = param3;
        }

        @Override
        public final int hashCode() {
            return Objects.hash(
                super.hashCode(),
                this.param2,
                this.param3);
        }
    }

    public static void main(String [] args) {

        A a = new A("A");
        B b = new B("A", "B", "C");

        System.out.println("A: " + a.hashCode());
        System.out.println("B: " + b.hashCode());
    }

}

最好的实现?这是一个很难回答的问题,因为这取决于使用模式。

Josh Bloch的Effective Java在第8项(第二版)中提出了几乎所有情况下合理的良好实现。最好的办法是去查一下,因为作者在那里解释了为什么这种方法是好的。

简短的版本

Create a int result and assign a non-zero value. For every field f tested in the equals() method, calculate a hash code c by: If the field f is a boolean: calculate (f ? 0 : 1); If the field f is a byte, char, short or int: calculate (int)f; If the field f is a long: calculate (int)(f ^ (f >>> 32)); If the field f is a float: calculate Float.floatToIntBits(f); If the field f is a double: calculate Double.doubleToLongBits(f) and handle the return value like every long value; If the field f is an object: Use the result of the hashCode() method or 0 if f == null; If the field f is an array: see every field as separate element and calculate the hash value in a recursive fashion and combine the values as described next. Combine the hash value c with result: result = 37 * result + c Return result

这将导致在大多数使用情况下哈希值的适当分布。

对于简单类,通常最容易基于equals()实现检查的类字段实现hashCode()。

public class Zam {
    private String foo;
    private String bar;
    private String somethingElse;

    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }

        if (obj == null) {
            return false;
        }

        if (getClass() != obj.getClass()) {
            return false;
        }

        Zam otherObj = (Zam)obj;

        if ((getFoo() == null && otherObj.getFoo() == null) || (getFoo() != null && getFoo().equals(otherObj.getFoo()))) {
            if ((getBar() == null && otherObj. getBar() == null) || (getBar() != null && getBar().equals(otherObj. getBar()))) {
                return true;
            }
        }

        return false;
    }

    public int hashCode() {
        return (getFoo() + getBar()).hashCode();
    }

    public String getFoo() {
        return foo;
    }

    public String getBar() {
        return bar;
    }
}

最重要的是保持hashCode()和equals()的一致性:如果equals()对于两个对象返回true,那么hashCode()应该返回相同的值。如果equals()返回false,那么hashCode()应该返回不同的值。