从 equals() 调用 hashCode()

Calling hashCode() from equals()

我已经为我的 class 定义了 hashCode(),其中包含一长串 class 属性。

根据合同,我还需要实现 equals(),但是是否可以通过比较 hashCode() 来简单地实现它,以避免所有额外的代码?这样做有什么危险吗?

例如

@Override
public int hashCode() 
{
    return new HashCodeBuilder(17, 37)
        .append(field1)
        .append(field2)
    // etc.
    // ...
}

@Override
public boolean equals(Object that) {
    // Quick special cases
    if (that == null) {
        return false;
    }
    if (this == that) {
        return true;
    }
    // Now consider all main cases via hashCode()
    return (this.hashCode() == that.hashCode());
}

不要那样做。

hashCode() 的合同规定两个相等的对象必须具有相同的哈希码。对于不相等的对象,它不提供任何保证。这意味着您可能有两个完全不同的对象,但碰巧碰巧具有相同的哈希码,从而破坏了您的 equals().

不难发现字符串之间的哈希码冲突。考虑 JDK 8 String.hashCode() 实现中的核心循环:

for (int i = 0; i < value.length; i++) {
    h = 31 * h + val[i];
}

其中h的初始值为0val[i]为第位置的字符的数值给定的字符串。例如,如果我们取一个长度为 3 的字符串,这个循环可以写成:

h = 31 * (31 * val[0] + val[1]) + val[2];

如果我们选择任意字符串,例如 "abZ",我们有:

h("abZ") = 31 * (31 * 'a' + 'b') + 'Z'
h("abZ") = 31 * (31 * 97 + 98) + 90
h("abZ") = 96345

然后我们可以从 val[1] 中减去 1,同时将 31 添加到 val[2],这将得到字符串 "aay":

h("aay") = 31 * (31 * 'a' + 'a') + 'y'
h("aay") = 31 * (31 * 97 + 97) + 121
h("aay") = 96345

导致碰撞:h("abZ") == h("aay") == 96345

另外请注意,您的 equals() 实现不会检查您是否在比较相同类型的对象。因此,假设您有 this.hashCode() == 96345,以下语句将 return true:

yourObject.equals(Integer.valueOf(96345))

这可能不是您想要的。

只比较对象的hashCode()绝对安全。

你的对象可以有比哈希码更多的不同状态:哈希码是一个 int,这意味着它被限制为 2^32 = 4,294,967,296 个可能的值,但你的对象可能有不止一个int 字段。

因此证明,可能有两个不同的对象(根据 equals)具有相同的哈希码。

但是当然,出于性能原因,您可以先比较哈希码(如果哈希码计算比字段比较快):如果哈希码不相等,则对象也不相等,因此您可以安全地returnfalse马上!