我读到在覆盖equals()时应该始终拥有code()。
谁能举一个实际的例子说明为什么否则它可能是错误的?即覆盖equals()而不是hashCode()时可能出现的问题。
每当我们覆盖 equals() 时,是否有必要编写一个健壮的 hasCode() 函数?还是一个简单的实现就足够了?
例如,
像下面这样一个糟糕的实现足以满足equals()和hashCode()之间的约定?
public int hashCode() {
return 91;
}
我读到在覆盖equals()时应该始终拥有code()。
谁能举一个实际的例子说明为什么否则它可能是错误的?即覆盖equals()而不是hashCode()时可能出现的问题。
每当我们覆盖 equals() 时,是否有必要编写一个健壮的 hasCode() 函数?还是一个简单的实现就足够了?
例如,
像下面这样一个糟糕的实现足以满足equals()和hashCode()之间的约定?
public int hashCode() {
return 91;
}
两者equals
都hashcode
基于对象的唯一性原则。如果equals
返回true
两个对象的哈希码必须相同,否则基于哈希的结构和算法可能会产生未定义的结果。
考虑一个基于哈希的结构,例如HashMap
. hashcode
将作为基础调用以获取密钥的引用,而不是equals
,因此在大多数情况下无法找到密钥。此外,糟糕的实现hashcode
会产生影响性能的冲突(多个对象具有相同的hashcode
,哪个是“正确的”?)。
恕我直言,覆盖equals
OR hashcode
(而不是覆盖两者)应该被视为代码异味,或者至少是潜在的错误源。也就是说,除非您 100% 确定它迟早不会影响您的代码(我们什么时候如此确定?)。
注意:有各种库通过拥有equals
和hashcode
构建器来提供对此的支持,例如Apache Commons和.HashcodeBuilder
EqualsBuilder
equals()
andhashCode()
在某些集合中连用,例如HashSet
and HashMap
,所以你必须确保如果你使用这些集合,你会hashCode
根据合同覆盖。
如果您根本不覆盖hashCode
,那么您将遇到问题HashSet
and HashMap
。特别是,两个“相等”的对象可能被放入不同的哈希桶中,即使它们应该相等。
如果您确实 override hashCode
,但做得很差,那么您将遇到性能问题。您的所有条目HashSet
和HashMap
将被放入同一个存储桶中,您将失去 O(1) 性能并取而代之的是 O(n)。这是因为数据结构本质上变成了一个线性检查的链表。
至于在这些条件之外破坏程序,这是不可能的,但你永远不知道 API(尤其是在 3rd 方库中)何时会依赖于这个合约。对于没有实现它们中的任何一个的对象,该契约是有效的,因此可以想象一个库可能会在某个地方依赖它而不使用哈希桶。
在任何情况下,实现一个好的hashCode
东西都很容易,特别是如果您使用的是 IDE。Eclipse 和 Netbeans 都有能力为你生成equals
和hashCode
为你遵循所有契约的方式,包括相反的规则equals
(断言 that a.equals(b) == b.equals(a)
)。您需要做的就是选择要包含的字段并继续。
下面是一些代码,说明了不实现 hashCode() 可以引入的错误: Set.contains() 将首先检查对象的 hashCode(),然后检查 .equals()。因此,如果您不同时实现两者, .contains() 将不会以直观的方式表现:
public class ContainsProblem {
// define a class that implements equals, without implementing hashcode
class Car {
private String name;
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Car)) return false;
Car car = (Car) o;
if (name != null ? !name.equals(car.name) : car.name != null) return false;
return true;
}
public String getName() {return name;}
public Car(String name) { this.name = name;}
}
public static void main(String[] args) {
ContainsProblem oc = new ContainsProblem();
ContainsProblem.Car ford = oc.new Car("ford");
ContainsProblem.Car chevy = oc.new Car("chevy");
ContainsProblem.Car anotherFord = oc.new Car("ford");
Set cars = Sets.newHashSet(ford,chevy);
// if the set of cars contains a ford, a ford is equal to another ford, shouldn't
// the set return the same thing for both fords? without hashCode(), it won't:
if (cars.contains(ford) && ford.equals(anotherFord) && !cars.contains(anotherFord)) {
System.out.println("oh noes, why don't we have a ford? isn't this a bug?");
}
}
}
您的简单实现是正确的,但会降低基于哈希的集合的性能。
如果您的类的两个不同实例比较相等,则默认实现(由 提供Object
)将违反合同。
我建议阅读 Joshua Bloch 的“Effective Java”第 3 章“所有对象的通用方法”。没有人能比他解释得更好。He He领导了众多Java平台特性的设计和实现。