繁体   English   中英

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

[英]Why is there a need to override hashcode if I override the 'equals' method in Java?

我知道只要在Java中重写equals方法,就需要覆盖hashcode。 那只是一份合同。 我试图理解这背后的逻辑。 我正在阅读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)));
    }
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

转到此链接

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

对于你的问题。

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

这里查看Object#hashCode文档

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

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

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

新PhoneNumber(707,867,5309)

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

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

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

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

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方法。

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM