6

我知道只要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)));
    }
}

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

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

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

4

6 回答 6

4

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

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

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

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

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

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

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

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

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

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

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

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

于 2013-08-24T05:08:53.257 回答
3

如果您不同时覆盖hashcodeequals则每个实例(例如 " new PhoneNumber(707, 867, 5309)")将具有不同的哈希码。

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

于 2013-08-24T05:08:38.250 回答
0

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

于 2013-10-05T00:01:00.130 回答
0

转到此链接

hashcode 用于维护合约并唯一标识 hashmap 或 hashtable 中的每个对象。

于 2013-08-24T05:06:17.960 回答
0

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

新电话号码(707、867、5309)

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

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

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

请将电话号码存储在一个变量中,并使用该变量来放置和获取,这样它就可以工作了——因为它是同一个对象,位于同一个内存位置,具有相同的 hashCode 并且等于 == 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方法。

于 2013-08-24T20:35:15.530 回答
0

对于你的问题。

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

在此处查看 Object#hashCode 文档

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

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

于 2013-08-24T09:47:56.713 回答