在equals()中调用hashCode()方法

5

我已经为我的类定义了hashCode(),其中包含了很长的属性列表。

按照契约,我还需要实现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());
}

11
哈希码冲突会发生什么? - resueman
除非您有一个双射哈希码函数(这是不太可能的),否则实现equals的方式并不好。正如@resueman所说,如果发生冲突,您将得到两个不同的对象相等,因为它们具有相同的哈希码值。此外,您的equals方法没有检查要比较的实例是否具有相同的类型,您可以将自己的实例与具有相同哈希码值的字符串进行比较,并返回true。我想这绝对不是您想要的... - Alexis C.
2
如果两个对象根据equals()方法是相等的,那么它们必须具有相同的hashCode()值,但反过来则不一定成立。因此,不建议通过比较hashcode()来实现equals()方法。 - Kishore Kirdat
值得注意的是,即使在这种情况下这是一种有效的方法,你的实现也会失败,因为它没有检查对象是否是相同类型。 - resueman
你应该了解一下"AutoValue"。它可以自动生成#equals()#hashCode()#toString()方法,省去了手动编写的烦恼。"AutoValue" - Tom
2个回答

14

不要这样做。

hashCode() 的契约规定相等的两个对象必须具有相同的哈希码。这并不能保证对于不相等的对象会发生什么。这意味着你可能会有两个完全不同的对象,但是由于机会原因,它们具有相同的哈希码,从而破坏了你的equals()方法。

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

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

h的初始值为0val[i]为给定字符串中第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,下面的语句将返回true

yourObject.equals(Integer.valueOf(96345))

这可能不是您想要的。


谢谢,但在哪种情况下会出现差异?如果我的所有字段都是字符串,那么从技术上讲我会很安全吗?(但我明白通常不应该这样做) - gene b.
1
@dystroy 的推断是公正的,因为OP正在使用HashCodeBuilder来构建哈希码,它指向的是那个没有强制执行哈希函数双射的commons类。 - Giovanni Botta
4
@geneb。不,字符串容易发生很多哈希冲突。 - RealSkeptic
3
如果你的对象有超过 2^32 种可能的值,那么必然会存在哈希碰撞。字符串的可能数量已经超过了 2^32。 - Louis Wasserman
谢谢,我原本想问为什么需要hashCode(),如果它不能保证区分,但现在我明白了,它用于在Collections中进行快速的contains()查找,只有当发现多个对象时,才使用equals()来区分。 - gene b.

3

仅仅比较对象的hashCode()是绝对不安全的。

你的对象可能具有更多不同的状态,而哈希码只是一个int,这意味着它仅限于2^32 = 4,294,967,296个可能的值,但你的对象可能具有不止一个单独的int字段。

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

但是,出于性能原因(如果哈希码计算比字段比较快),你可以首先比较哈希码:如果哈希码不相等,则对象也不相等,因此你可以立即安全地返回false


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接