Java 重写equals()时重写hashCode()
可能重复:Java 重写equals()时重写hashCode(),java,Java,可能重复: 我读到,当重写equals()时,应该始终使用code() 有人能举一个实际的例子说明为什么它可能是错误的吗? i、 e.重写equals()而不是hashCode()时可能出现的问题 每当我们重写equals()时,是否有必要编写一个健壮的hasCode()函数?或者一个微不足道的实现就足够了 比如说, 下面这种糟糕的实现足以满足equals()和hashCode()之间的约定 equals()和hashCode()在某些集合中结合使用,例如HashSet和HashMap,因此您
我读到,当重写equals()时,应该始终使用code() 有人能举一个实际的例子说明为什么它可能是错误的吗? i、 e.重写equals()而不是hashCode()时可能出现的问题 每当我们重写equals()时,是否有必要编写一个健壮的hasCode()函数?或者一个微不足道的实现就足够了 比如说, 下面这种糟糕的实现足以满足equals()和hashCode()之间的约定
equals()
和hashCode()
在某些集合中结合使用,例如HashSet
和HashMap
,因此您必须确保如果使用这些集合,则根据合同覆盖hashCode
如果根本不重写hashCode
,那么HashSet
和HashMap
就会出现问题。特别是,两个“相等”的对象可以放在不同的哈希桶中,即使它们应该相等
如果您确实重写了hashCode,但做得很差,那么您将遇到性能问题。HashSet
和HashMap
的所有条目都将放在同一个存储桶中,您将失去O(1)性能,取而代之的是O(n)。这是因为数据结构本质上变成了一个线性检查的链表
至于在这些条件之外破坏程序,这是不可能的,但您永远不知道API(特别是在第三方库中)何时会依赖于此合同。对于未实现其中任何一个的对象,都支持该契约,因此可以想象,库可能在某个地方依赖它,而不使用散列桶
在任何情况下,实现一个好的
hashCode
都很容易,尤其是在使用IDE的情况下。Eclipse和Netbeans都能够以遵循所有契约的方式为您生成equals
和hashCode
,包括equals
的逆规则(断言a.equals(b)==b.equals(a)
)。您所需要做的就是选择要包含的字段并执行。这两个equals
和hashcode
都基于对象的唯一性原则。如果equals
返回true
两个对象的hashcode必须相同,否则基于hash的结构和算法可能会有未定义的结果
考虑一个基于哈希的结构,例如哈希映射
hashcode
将被调用作为获取密钥引用的基础,而不是等于
,这使得在大多数情况下无法找到密钥。此外,hashcode
的糟糕实现将产生影响性能的冲突(多个对象具有相同的hashcode
,哪一个是“正确的”对象?)
IMHO,重写等于
或哈希代码
(而不是同时重写两者)应该被视为代码气味,或者至少是潜在的bug源。也就是说,除非你100%确定它迟早不会影响你的代码(我们什么时候这么确定?)
注意:有各种库通过使用
equals
和hashcode
构建器来支持这一点,就像HashcodeBuilder
和equalBuilder
您的简单实现是正确的,但会破坏基于哈希的集合的性能
如果类的两个不同实例比较相等,那么默认实现(由
对象提供)将破坏契约。我建议阅读Joshua Bloch的“有效Java”第3章“所有对象通用的方法”。没有人能比他解释得更好。他领导了许多Java平台功能的设计和实现。以下是一些代码,说明了不实现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?");
}
}
}
@是的,我看到所有的值都在一个散列桶中。如果我们不考虑性能,还有其他问题吗?或者更准确地说,它在任何情况下都会破坏程序的功能吗?据我所知,它不会破坏任何东西。我相信您不会在集合中使用它。在Java 7中,还有一个类及其散列方法。@Brian确实!我太习惯Apache Commons了,以至于忘了那个;)谢谢你的回答。我认为,您已经给出了一个公平的解释,当它可能是一个执行不佳的问题时。
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?");
}
}
}