当我重写equals()方法时,为什么需要重写hashCode()方法?

36

好的,我从多个地方了解到,每当我覆盖equals()方法时,我也需要覆盖hashCode()方法。但请考虑以下代码片段:

package test;

public class MyCustomObject {

    int intVal1;
    int intVal2;

    public MyCustomObject(int val1, int val2){
        intVal1 = val1;
        intVal2 = val2;
    }

    public boolean equals(Object obj){
        return (((MyCustomObject)obj).intVal1 == this.intVal1) && 
                (((MyCustomObject)obj).intVal2 == this.intVal2);
    }

    public static void main(String a[]){
        MyCustomObject m1 = new MyCustomObject(3,5);
        MyCustomObject m2 = new MyCustomObject(3,5);
        MyCustomObject m3 = new MyCustomObject(4,5);

        System.out.println(m1.equals(m2));
        System.out.println(m1.equals(m3));
    }
}

这里的输出是true和false,正好符合我的要求,我并不介意覆盖hashCode()方法。这意味着覆盖hashCode()方法是一种选择而不是必须的,就像每个人所说的那样。

我想要第二个确认。


可能是https://dev59.com/yXRC5IYBdhLWcg3wOeSB的重复问题,为什么在C#中覆盖Equals方法时重写GetHashCode方法很重要 - Oded
6
不是那个的复制;那是C#,这是Java。(问题非常相似,甚至可能完全相同,但仍然不同。) - Thomas
5个回答

34

由于您的代码未使用任何需要hashCode() API的功能(HashMap,HashTable),因此它对您有效。

但是,您不知道您的类(可能不是作为一次性编写的)是否会在以后被调用,而该调用确实将其对象用作哈希键,这样会受到影响。

根据Object类的文档

hashCode的通用合同是:

  • 在Java应用程序的执行期间,在同一对象上多次调用hashCode方法时,只要对对象上使用的equals比较的信息没有修改,hashCode方法就必须始终返回相同的整数。这个整数不需要从应用程序的一个执行到同一应用程序的另一个执行保持一致。

  • 如果两个对象根据equals(Object)方法是相等的,则在每个对象上调用hashCode方法必须产生相同的整数结果


1
可选地,hashCode 最好尽量不在同一程序执行期间经常出现在一起的许多值上返回相同的整数。我曾经有一个性能 bug,花费了我很长时间才追踪到是使用物理相等性和默认(结构)哈希函数。这种用法满足上述条件,但所有结构相同但物理上不同的值都被散列到相同的桶中。 - Pascal Cuoq
@Pascal - 正确。事实上,文档继续说:
  • 如果根据equals(java.lang.Object)方法两个对象不相等,则不要求在每个对象上调用hashCode方法必须产生不同的整数结果。然而,程序员应该意识到,对于不相等的对象产生不同的整数结果可能会提高哈希表的性能。
- DVK
“在合理可行的范围内,Object 类定义的 hashCode 方法确实会针对不同的对象返回不同的整数。(通常是通过将对象的内部地址转换为整数来实现的,但这种实现技术并不是 Java 编程语言所必需的。)” - DVK

11

因为 HashMap/Hashtable 首先会通过 hashCode() 查找对象。

如果它们不相同,HashMap 会断定这两个对象不相同并返回该映射中不存在。


6
您需要同时或者都不使用@Override的原因是它们与API的相互关系。如果您将m1放入HashSet<MyCustomObject>中,那么它就不会包含m2。这种不一致的行为会导致许多错误和混乱。Java库有大量功能,为了使它们为您工作,您需要遵循规则,并确保equalshashCode是一致的,这是最重要的之一。

4

大部分其他评论已经给出了答案:你需要这样做是因为有一些集合(例如:HashSet、HashMap)使用hashCode作为优化来“索引”对象实例,而这些优化期望如果:a.equals(b) ==> a.hashCode() == b.hashCode()(请注意反过来不成立)。

但是作为额外的信息,你可以进行以下练习:

class Box {
     private String value;
     /* some boring setters and getters for value */
     public int hashCode() { return value.hashCode(); }
     public boolean equals(Object obj) { 
           if (obj != null && getClass().equals(obj.getClass()) { 
               return ((Box) obj).value.equals(value); 
            } else { return false; }
     }
}

执行以下操作:

Set<Box> s = new HashSet<Box>();
Box b = new Box();
b.setValue("hello");
s.add(b);
s.contains(b); // TRUE
b.setValue("other");
s.contains(b); // FALSE
s.iterator().next() == b // TRUE!!! b is in s but contains(b) returns false

从这个例子中,我们可以学到一个教训:用可变属性来实现equalshashCode是一个非常糟糕的想法。

0

在使用集合(如HashMap、HashSet等)中的hashCode()值搜索对象时,这一点尤为重要。每个对象返回不同的hashCode()值,因此您必须覆盖此方法,以便根据对象状态一致地生成hashCode值,帮助集合算法在哈希表上定位值。


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