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


当前回答

标准实现很弱,使用它会导致不必要的冲突。想象一个

class ListPair {
    List<Integer> first;
    List<Integer> second;

    ListPair(List<Integer> first, List<Integer> second) {
        this.first = first;
        this.second = second;
    }

    public int hashCode() {
        return Objects.hashCode(first, second);
    }

    ...
}

Now,

new ListPair(List.of(a), List.of(b, c))

and

new ListPair(List.of(b), List.of(a, c))

List的乘数具有相同的hashCode,即31*(a+b) + c。hashCode在这里被重用。显然,碰撞是不可避免的,但产生不必要的碰撞只是……不必要的。

There's nothing substantially smart about using 31. The multiplier must be odd in order to avoid losing information (any even multiplier loses at least the most significant bit, multiples of four lose two, etc.). Any odd multiplier is usable. Small multipliers may lead to faster computation (the JIT can use shifts and additions), but given that multiplication has latency of only three cycles on modern Intel/AMD, this hardly matters. Small multipliers also leads to more collision for small inputs, which may be a problem sometimes.

使用质数是没有意义的,因为质数在环Z/(2**32)中没有意义。

因此,我建议使用随机选择的大奇数(可以选择质数)。由于i86/amd64 cpu可以使用更短的指令来匹配一个有符号字节的操作数,因此对于像109这样的乘法器来说,速度优势很小。为了最小化冲突,可以使用类似0x58a54cf5的值。

在不同的地方使用不同的乘数是有帮助的,但可能不足以证明额外的工作是合理的。

其他回答

这里有一个非常严重的bug。

Zam obj1 = new Zam("foo", "bar", "baz");
Zam obj2 = new Zam("fo", "obar", "baz");

同样的hashcode

你可能想要

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

(现在你能在Java中直接从int获取hashCode吗?我认为它做了一些自动铸造。如果是这种情况,跳过toString,它很难看。)

关于8.blogspot.com,你说过

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

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

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

but

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

我更喜欢使用实用工具方法从谷歌集合库从类对象,帮助我保持我的代码干净。equals和hashcode方法通常都是从IDE的模板中创建的,所以它们的可读性不太好。

对于简单类,通常最容易基于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()应该返回不同的值。

虽然这链接到Android文档(Wayback Machine)和我自己在Github上的代码,但它一般适用于Java。我的答案是dmeister的答案的扩展,只是代码更容易阅读和理解。

@Override 
public int hashCode() {

    // Start with a non-zero constant. Prime is preferred
    int result = 17;

    // Include a hash for each field.

    // Primatives

    result = 31 * result + (booleanField ? 1 : 0);                   // 1 bit   » 32-bit

    result = 31 * result + byteField;                                // 8 bits  » 32-bit 
    result = 31 * result + charField;                                // 16 bits » 32-bit
    result = 31 * result + shortField;                               // 16 bits » 32-bit
    result = 31 * result + intField;                                 // 32 bits » 32-bit

    result = 31 * result + (int)(longField ^ (longField >>> 32));    // 64 bits » 32-bit

    result = 31 * result + Float.floatToIntBits(floatField);         // 32 bits » 32-bit

    long doubleFieldBits = Double.doubleToLongBits(doubleField);     // 64 bits (double) » 64-bit (long) » 32-bit (int)
    result = 31 * result + (int)(doubleFieldBits ^ (doubleFieldBits >>> 32));

    // Objects

    result = 31 * result + Arrays.hashCode(arrayField);              // var bits » 32-bit

    result = 31 * result + referenceField.hashCode();                // var bits » 32-bit (non-nullable)   
    result = 31 * result +                                           // var bits » 32-bit (nullable)   
        (nullableReferenceField == null
            ? 0
            : nullableReferenceField.hashCode());

    return result;

}

EDIT

通常,当重写hashcode(…)时,还需要重写equals(…)。所以对于那些将要或已经实现等于的人,这里有一个来自我的Github的很好的参考…

@Override
public boolean equals(Object o) {

    // Optimization (not required).
    if (this == o) {
        return true;
    }

    // Return false if the other object has the wrong type, interface, or is null.
    if (!(o instanceof MyType)) {
        return false;
    }

    MyType lhs = (MyType) o; // lhs means "left hand side"

            // Primitive fields
    return     booleanField == lhs.booleanField
            && byteField    == lhs.byteField
            && charField    == lhs.charField
            && shortField   == lhs.shortField
            && intField     == lhs.intField
            && longField    == lhs.longField
            && floatField   == lhs.floatField
            && doubleField  == lhs.doubleField

            // Arrays

            && Arrays.equals(arrayField, lhs.arrayField)

            // Objects

            && referenceField.equals(lhs.referenceField)
            && (nullableReferenceField == null
                        ? lhs.nullableReferenceField == null
                        : nullableReferenceField.equals(lhs.nullableReferenceField));
}