如果我覆盖Java中的'equals'方法,为什么需要重写hashcode?

时间:2013-08-24 05:01:12

标签: java object equals hashcode effective-java

我知道只要在Java中覆盖equals方法,就需要覆盖哈希码。那只是一份合同。我试图理解这背后的逻辑。我正在阅读Joshua Bloch的有效Java,我遇到了这段代码(第9项,第45页):

import java.util.HashMap;
import java.util.Map;

public final class PhoneNumber {
    private final short areaCode;
    private final short prefix;
    private final short lineNumber;

    public PhoneNumber(int areaCode, int prefix, int lineNumber) {
        rangeCheck(areaCode, 999, "area code");
        rangeCheck(prefix, 999, "prefix");
        rangeCheck(lineNumber, 9999, "line number");
        this.areaCode = (short) areaCode;
        this.prefix = (short) prefix;
        this.lineNumber = (short) lineNumber;
    }

    private static void rangeCheck(int arg, int max, String name) {
        if (arg < 0 || arg > max)
            throw new IllegalArgumentException(name + ": " + arg);
    }

    @Override
    public boolean equals(Object o) {
        if (o == this)
            return true;
        if (!(o instanceof PhoneNumber))
            return false;
        PhoneNumber pn = (PhoneNumber) o;
        return pn.lineNumber == lineNumber && pn.prefix == prefix
                && pn.areaCode == areaCode;
    }

    // Broken - no hashCode method!

    // A decent hashCode method - Page 48
    // @Override public int hashCode() {
    // int result = 17;
    // result = 31 * result + areaCode;
    // result = 31 * result + prefix;
    // result = 31 * result + lineNumber;
    // return result;
    // }

    // Lazily initialized, cached hashCode - Page 49
    // private volatile int hashCode; // (See Item 71)
    //
    // @Override public int hashCode() {
    // int result = hashCode;
    // if (result == 0) {
    // result = 17;
    // result = 31 * result + areaCode;
    // result = 31 * result + prefix;
    // result = 31 * result + lineNumber;
    // hashCode = result;
    // }
    // return result;
    // }

    public static void main(String[] args) {
        Map<PhoneNumber, String> m = new HashMap<PhoneNumber, String>();
        m.put(new PhoneNumber(707, 867, 5309), "Jenny");
        System.out.println(m.get(new PhoneNumber(707, 867, 5309)));
    }
}

这是他在文中提到的,我很难理解。

  

此时,您可能希望m.get(new PhoneNumber(707, 867, 5309))返回“Jenny”,但它返回null。请注意两个   涉及PhoneNumber实例:一个用于插入   HashMap和第二个相等的实例用于(尝试)   恢复。 PhoneNumber类无法覆盖hashCode导致   这两个相等的实例具有不相等的哈希码,违反了   哈希码契约。因此get方法很可能会寻找   电话号码与其中的电话号码不同   由put方法存储

我不明白他谈到的两个PhoneNumber实例是什么。我只在m.put(new PhoneNumber(707, 867, 5309), "Jenny")中创建了一个实例。此外,我再次查找此对象,即使它从Object Class继承hashCode方法,也应该返回相同的哈希码。 为什么会这样?这里的一些解释会有很多帮助。

6 个答案:

答案 0 :(得分:4)

  

我不明白他谈到的两个PhoneNumber实例是什么。

第一个是你用来插入的那个。

m.put(new PhoneNumber(707, 867, 5309), "Jenny"));

第二个实例是用于检索的实例。

m.get(new PhoneNumber(707, 867, 5309)); // Notice the use of "new"
  

此外,我再次查找此对象,即使它从Object Class继承hashCode方法,也应返回相同的哈希码。

这是不正确的。 hashCode()类中的Object的默认实现为不同的对象返回不同的整数,因为它是通过将对象的内部地址转换为整数来实现的。因此,哈希码检查在那里失败。

另一方面,如果您尝试使用相同的实例检索PhoneNumber

PhoneNumber phoneNum = new PhoneNumber(707, 867, 5309);
m.put(phoneNum, "Jenny"));
m.get(phoneNum); // Not NULL

哈希码检查将通过(因为,您使用了之前插入的相同对象)和equals()。这当然不是推荐的方法,因为我们更有可能使用与用于插入的关键对象不同的关键对象。

然而,使用的密钥将是有意义的等价物(如不同的String对象,但其文本相同),因此需要提供hashCode()实现才能使匹配和检索正确发生。

另见:Checking for and Removing elements in Java HashMap

答案 1 :(得分:3)

如果您没有覆盖hashcode以及equals,那么每个实例,例如“new PhoneNumber(707, 867, 5309)"将使用不同的哈希码。

因此,从HashMap的角度来看,它们将被视为两个不同的条目。 只需阅读有关hashmap如何工作的更多信息。因此,如果两个可能相同但具有不同hascode的对象将存储在不同的存储桶中。

答案 2 :(得分:0)

转到此link

哈希码用于维护契约并唯一标识hashmap或hashtable中的每个对象。

答案 3 :(得分:0)

对于你的问题。

  

此外,我再次查找此对象,即使它从Object Class继承hashCode方法,也应返回相同的哈希码。

检查Object#hashCode文档here

  

尽可能合理,the hashCode method defined by class Object does return distinct integers for distinct objects。 (这通常通过将对象的内部地址转换为整数来实现,但JavaTM编程语言不需要此实现技术。)

换句话说,除非你覆盖它,否则hashCode不会为两个对象返回相同的整数。

答案 4 :(得分:0)

您认为您只有一个实例,但您有两个实例。每个

  

new PhoneNumber(707,867,5309)

在另一个内存位置创建一个实例。哈希映射方法 m.get 正在查找您在方法调用中创建的新实例。此实例具有 m.put 方法中第一个创建的实例的另一个哈希码。

(因为有两个对象实例,Java将在超类Object中计算不同的hashCode。)

因此哈希映射找不到第二个对象的hashCode的第一个对象。

请将电话号码存储在一个变量中,并使用该变量进行放置,以便它可以正常工作 - 因为它是同一个内存位置的相同对象,具有相同的hashCode和equals == true。 / p>

public static void main(String[] args) {
    Map<PhoneNumber, String> m = new HashMap<PhoneNumber, String>();
    PhoneNumber num = new PhoneNumber(707, 867, 5309);
    m.put(num, "Jenny");
    System.out.println(m.get(num));
}

但是对于实际使用情况,您必须正确实施hashCodeequals方法。

答案 5 :(得分:0)

hashCode()的目的是快速识别物体不相等的东西;对hashCode()的一次调用将立即显示一个对象不等于已调用hashCode()方法且返回不同值的任何对象。这是一个非常强大的功能,但它要求任何两个“相等”的对象必须返回相同的hashCode值。如果两个对象没有返回相同的hashCode值,则某些集合类型将假定它们可能不相等,并且不会打扰调用equals以查看它们是否可能。