如果我在Java中覆盖“等于”方法,为什么需要覆盖哈希码?

bra*_*orm 5 java equals object hashcode effective-java

我知道只要equals在Java中重写该方法,就需要重写哈希码。那仅仅是合同。我试图了解其背后的逻辑。我正在阅读Joshua Bloch的 * Effective 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)));
    }
}
Run Code Online (Sandbox Code Playgroud)

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

此时,您可能希望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方法,它也应返回相同的哈希码。为什么会这样?这里的一些解释会很有帮助。

Rav*_*yal 4

我不明白他所说的两个 PhoneNumber 实例是什么。

第一个是您用于插入的。

m.put(new PhoneNumber(707, 867, 5309), "Jenny"));
Run Code Online (Sandbox Code Playgroud)

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

m.get(new PhoneNumber(707, 867, 5309)); // Notice the use of "new"
Run Code Online (Sandbox Code Playgroud)

另外,我再次查找该对象,即使它继承了对象类的 hashCode 方法,它也应该返回相同的哈希码。

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

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

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

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

然而,所使用的键在意义上是等效的(就像不同的 String 对象,但其文本相同),因此hashCode()需要提供实现才能正确进行匹配和检索。

另请参阅:检查和删除 Java HashMap 中的元素