如果我在Java中覆盖“等于"方法,为什么需要覆盖哈希码? [英] Why is there a need to override hashcode if I override the 'equals' method in Java?

查看:150
本文介绍了如果我在Java中覆盖“等于"方法,为什么需要覆盖哈希码?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我知道每当Java中重写equals方法时,都需要重写哈希码.那仅仅是合同.我试图了解其背后的逻辑.我正在通过 Joshua Bloch 阅读* Effective Java,我遇到了这段代码(项目9,第45页):

I know there is a need to override hashcode whenever the equals method is overridden in Java. That is merely a contract. I am trying to understand the logic behind this. I was reading *Effective Java by Joshua Bloch, and I came across this code (Item 9, page 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)));
    }
}

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

This is what he mentions in the text, which I am having difficulty understanding.

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

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")中仅创建了一个实例.另外,我再次寻找该对象,即使该对象从Object Class继承了hashCode方法,该对象也应返回相同的哈希码. 为什么会这样?这里的一些解释会很有帮助.

I don't understand what the two PhoneNumber instances he talks about. There is only instance that I create in m.put(new PhoneNumber(707, 867, 5309), "Jenny"). Also I look for this object again, which should return the same hashcode even if it inherits the hashCode method from Object Class. Why does this happen? Some explanation here would help a lot.

推荐答案

我不明白他所说的两个PhoneNumber实例.

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

第一个是您用于插入的内容.

The first one is the one you used for insertion.

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

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

The second instance is the one used for retrieval.

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

我还要再次查找该对象,即使该对象从对象类继承了hashCode方法,该对象也应返回相同的哈希码.

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()的默认实现为不同的对象返回不同的整数,因为它是通过将对象的内部地址转换为整数来实现的.因此,哈希代码检查在那里失败.

That's incorrect. The default implementation of hashCode() in Object class returns distinct integers for distinct objects because it is implemented by converting the internal address of the object into an integer. Hence, the hash code check fails there.

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

If on the other hand, you had tried to retrieve the PhoneNumber using the same instance

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

哈希代码检查将通过(因为您使用了之前插入的同一对象),并且equals()也通过了.当然,不建议使用这种方法,因为与插入所使用的键对象相比,我们更有可能使用不同的键对象.

The hash code check would pass (since, you've used the same object that was inserted before) and the equals() as well. This of course isn't the recommended approach because we're far more likely to use a different key object than the one used for insertion.

但是,所使用的键在意义上是等效的(就像另一个String对象,但其文本相同),因此必须提供hashCode()实现,才能正确进行匹配和检索.

The key used would, however, be meaningfully equivalent (like a different String object but whose text is the same) and hence providing a hashCode() implementation is required for the match and retrieval to happen correctly.

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

这篇关于如果我在Java中覆盖“等于"方法,为什么需要覆盖哈希码?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆