3

可能重复:
在 Java 中,为什么 equals() 和 hashCode() 必须一致?

我读到在覆盖equals()时应该始终拥有code()。

谁能举一个实际的例子说明为什么否则它可能是错误的?即覆盖equals()而不是hashCode()时可能出现的问题。

每当我们覆盖 equals() 时,是否有必要编写一个健壮的 hasCode() 函数?还是一个简单的实现就足够了?

例如,

像下面这样一个糟糕的实现足以满足equals()和hashCode()之间的约定?

public int hashCode() {
   return 91;
}
4

5 回答 5

4

两者equalshashcode基于对象的唯一性原则。如果equals返回true两个对象的哈希码必须相同,否则基于哈希的结构和算法可能会产生未定义的结果。

考虑一个基于哈希的结构,例如HashMap. hashcode将作为基础调用以获取密钥的引用,而不是equals,因此在大多数情况下无法找到密钥。此外,糟糕的实现hashcode会产生影响性能的冲突(多个对象具有相同的hashcode,哪个是“正确的”?)。

恕我直言,覆盖equalsOR hashcode(而不是覆盖两者)应该被视为代码异味,或者至少是潜在的错误源。也就是说,除非您 100% 确定它迟早不会影响您的代码(我们什么时候如此确定?)。

注意:有各种库通过拥有equalshashcode构建器来提供对此的支持,例如Apache Commons和.HashcodeBuilderEqualsBuilder

于 2012-12-10T16:07:40.680 回答
2

equals()andhashCode()在某些集合中连用,例如HashSetand HashMap,所以你必须确保如果你使用这些集合,你会hashCode根据合同覆盖。

如果您根本不覆盖hashCode,那么您将遇到问题HashSetand HashMap。特别是,两个“相等”的对象可能被放入不同的哈希桶中,即使它们应该相等。

如果您确实 override hashCode,但做得很差,那么您将遇到性能问题。您的所有条目HashSetHashMap将被放入同一个存储桶中,您将失去 O(1) 性能并取而代之的是 O(n)。这是因为数据结构本质上变成了一个线性检查的链表。

至于在这些条件之外破坏程序,这是不可能的,但你永远不知道 API(尤其是在 3rd 方库中)何时会依赖于这个合约。对于没有实现它们中的任何一个的对象,该契约是有效的,因此可以想象一个库可能会在某个地方依赖它而不使用哈希桶。

在任何情况下,实现一个好的hashCode东西都很容易,特别是如果您使用的是 IDE。Eclipse 和 Netbeans 都有能力为你生成equalshashCode为你遵循所有契约的方式,包括相反的规则equals(断言 that a.equals(b) == b.equals(a))。您需要做的就是选择要包含的字段并继续。

于 2012-12-10T16:06:11.697 回答
2

下面是一些代码,说明了不实现 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?");
    }
}
}
于 2012-12-10T16:29:36.280 回答
1

您的简单实现是正确的,但会降低基于哈希的集合的性能。

如果您的类的两个不同实例比较相等,则默认实现(由 提供Object)将违反合同。

于 2012-12-10T16:08:53.223 回答
0

我建议阅读 Joshua Bloch 的“Effective Java”第 3 章“所有对象的通用方法”。没有人能比他解释得更好。He He领导了众多Java平台特性的设计和实现。

于 2012-12-10T16:12:07.507 回答