在覆盖equals()方法时覆盖hashCode()方法

3
可能重复:
在Java中,为什么equals()和hashCode()必须一致? 我了解到,在覆盖equals()方法时,应该始终重写hashCode()方法。是否可以给出一个实际的例子来说明否则会有什么问题呢?
那么,当我们覆盖equals()方法但没有重写hashCode()方法时,可能会出现哪些问题呢?
重写equals()方法是否需要编写健壮的hashCode()函数?还是一个简单的实现就足够了呢?
例如:
下面这个差劲的实现是否已经足以满足equals()和hashCode()之间的契约了?
public int hashCode() {
   return 91;
}

https://dev59.com/M3VD5IYBdhLWcg3wTJrF - Aviram Segal
@Blam 是的,我看到所有的值都将在一个哈希桶中。如果我们排除性能因素,那么还有其他问题吗?或者更准确地说,在任何情况下它会破坏程序的功能吗? - user1885220
据我所知,这不会破坏任何东西。我相信你不会将其用于集合中。 - paparazzo
5个回答

4

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

想象一个基于哈希的结构,比如HashMap。获取键引用时将调用hashcode,而不是equals,这使得在大多数情况下无法找到键。此外,hashcode的实现不良会创建冲突(具有相同hashcode的多个对象,哪个才是“正确”的对象?),从而影响性能。

在我看来,覆盖equalshashcode(而不是两者都覆盖)应该被视为代码异味或至少是潜在的错误源。也就是说,除非您百分之百确定它不会在早晚某个时候影响您的代码(我们何时能如此确定呢?)。

注意:有各种库可以通过具有 equalshashcode 构建器来提供支持,例如 Apache Commons 中的 HashcodeBuilderEqualsBuilder

在Java 7中,还有一个名为Objects的类,它具有其哈希方法。 - Brian
@Brian 确实!我太习惯使用Apache Commons了,以至于忘记了那个 ;) - Fritz

2

下面的代码展示了一个bug,它是由于没有实现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?");
    }
}
}

2
equals()hashCode()在某些集合中(如HashSetHashMap)被一起使用,因此您必须确保如果您使用这些集合,则根据协议覆盖hashCode
如果您根本不覆盖hashCode,那么您将在HashSetHashMap中遇到问题。特别是,尽管两个对象应该相等,但它们可能会被放置在不同的哈希桶中。
如果您覆盖了hashCode,但做得很差,那么您将遇到性能问题。所有HashSetHashMap的条目都将被放入同一个桶中,您将失去O(1)的性能并代之以O(n)。这是因为数据结构本质上变成了线性检查的链表。
至于在这些条件之外破坏程序的可能性不大,但您永远不知道API(特别是第三方库)是否依赖于此协议。对于没有实现它们的对象,该协议得到维护,因此有可能某个库在某个地方依赖于此而不使用哈希桶。
无论如何,实现一个好的hashCode很容易,特别是如果您使用IDE。Eclipse和Netbeans都可以为您生成equalshashCode,以确保遵循所有协议,包括equals的反向规则(即a.equals(b) == b.equals(a))。您只需要选择要包含的字段并开始即可。

谢谢你的回答。我认为你已经给出了一个公正的解释,说明了当实现不佳时可能会出现问题。 - user1885220

1

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

默认实现(由Object提供)如果两个不同的实例比较相等,则会违反契约。


0
我建议阅读Joshua Bloch的《Effective Java》第3章“所有对象都通用的方法”。没有人比他更能解释了。他曾领导过众多Java平台功能的设计和实现。

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