从 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
的初始值为0
,val[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
马上!
我已经为我的 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
的初始值为0
,val[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
马上!