【问题标题】:Why is there a need to override hashcode if I override the 'equals' method in Java?如果我覆盖 Java 中的“equals”方法,为什么需要覆盖哈希码?
【发布时间】:2013-08-24 05:01:12
【问题描述】:

我知道每当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 方法。 为什么会这样?这里的一些解释会很有帮助。

【问题讨论】:

  • 你会在这里找到一个很好的解释thejavageek.com/2013/06/28/significance-of-equals-and-hashcode
  • 以一种非常非技术性的方式思考它;您可以将哈希码视为相等的(快速)提示,而 equals 方法(缓慢)解决问题。如果两者不同步,那么显然可能会发生疯狂的事情

标签: java object equals hashcode effective-java


【解决方案1】:

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

第一个是你用来插入的。

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

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

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

我也再次寻找这个对象,即使它继承了 Object Class 的 hashCode 方法,它也应该返回相同的 hashcode。

这是不正确的。 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

【讨论】:

  • @BalusC 哇,我很高兴我的个人资料引起了你的注意,然后我阅读了链接,所有的兴奋都烟消云散了:(实际上,印度所有的猎头公司仍在寻找以 J2EE 作为他们的关键字,所以我的实际简历将其作为 JEE/J2EE :) 而且,将其作为 Java/Java EE 看起来有点奇怪,并且弄乱了我的 Justify 文本对齐方式在悬停弹出配置文件框中进行。我对对齐有点强迫症:) 对不起,如果这对你来说太闲了。
【解决方案2】:

如果您不覆盖 hashcodeequals 那么每个实例,例如"new PhoneNumber(707, 867, 5309)",会有不同的哈希码。

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

【讨论】:

    【解决方案3】:

    去这个link

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

    【讨论】:

      【解决方案4】:

      对于您的问题。

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

      查看 Object#hashCode 文档here

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

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

      【讨论】:

        【解决方案5】:

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

        【讨论】:

          【解决方案6】:

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

          新电话号码(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 方法。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2019-01-22
            相关资源
            最近更新 更多