java - 如果我在 Java 中覆盖 'equals' 方法,为什么需要覆盖 hashcode?

标签 java object equals hashcode effective-java

我知道每当在 Java 中重写 equals 方法时,都需要重写 hashcode。那只是一份契约(Contract)。我试图理解这背后的逻辑。我正在阅读 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)));
    }
}

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

At this point, you might expect m.get(new PhoneNumber(707, 867, 5309)) to return "Jenny", but it return null. Notice that two PhoneNumber instances are involved: one is used for insertion into the HashMap and a second, equal, instance is used for (attempted) retrieval. The PhoneNumber class's failure to override hashCode causes the two equal instances to have unequal hashcodes, in violation of the hashcode contract. Therefore the get method is likely to look for the phone number in a different hash bucket from the one in which it was stored by the put method

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

最佳答案

I don't understand what the two PhoneNumber instance he talks about.

第一个是您用于插入的那个。

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

第二个实例用于检索。

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

Also I look for this object again, which should return the same hashcode even if it inherits the hashCode method from Object Class.

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

如果另一方面,您曾尝试使用相同的实例检索 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

关于java - 如果我在 Java 中覆盖 'equals' 方法,为什么需要覆盖 hashcode?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/18415104/

相关文章:

c# - 如何在c#中动态设置数组长度

java - 如何在eclipse中获取notebook的java文件

java - JTabbedPane 的 MouseEvent

Java Point2D.Distance 与毕达哥拉斯定理不同?

java - ant构建脚本将参数传递给java任务

python - 包含 python 列表的类的多个实例

javascript - 如何按parentId JS对对象数组进行排序

java - 你能用==比较字符吗?

java - 没有自然键可用时 equals() 和 hashCode() 的实现?

java - 关于重写 java 中的 equals 方法