如果我在Java中重写了“equals”方法,为什么需要覆盖hashcode?

6

我知道在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")。我再次寻找这个对象,即使继承自Object类的hashCode方法,它应该返回相同的哈希码。为什么会发生这种情况?这里的一些解释将非常有帮助。

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

4
我不明白他所说的两个PhoneNumber实例是什么。
其中一个是你用于插入的。
m.put(new PhoneNumber(707, 867, 5309), "Jenny"));

第二个实例是用于检索的。
m.get(new PhoneNumber(707, 867, 5309)); // Notice the use of "new"

同时,我再次查找这个对象,即使它继承了Object类的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中的元素的检查和删除

@BalusC 噢,我很高兴我的个人资料引起了你的注意,但是当我看到链接后,所有的兴奋都消失了 :( 实际上,印度的所有猎头仍然在使用 J2EE 作为关键词进行搜索,所以我的实际简历中将其标记为 JEE/J2EE :) 而且,在这里将其标记为 Java/Java EE 看起来有点奇怪,并且会破坏我在悬停弹出框中所要对齐的文本。我有点强迫症,特别注重对齐 :) 如果这对你来说太啰嗦了,我很抱歉。 - Ravi K Thapliyal

3
如果您不重写hashCodeequals,那么每个实例(例如"new PhoneNumber(707, 867, 5309)")将具有不同的哈希码。因此,从HashMap的角度来看,它们将被视为两个不同的条目。请阅读更多关于hashmap工作原理的知识。所以,如果两个对象可能相等但具有不同的哈希码,则会存储在不同的桶中。

0

你可能认为你只有一个实例,但是你实际上有两个实例。每个

new PhoneNumber(707, 867, 5309)

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

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

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

请将电话号码存储在变量中,并使用该变量进行put和get操作,这样它将起作用-因为它是相同的对象,在相同的内存位置具有相同的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方法。


0

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


0

点击这个链接

哈希码用于维护合约并在哈希表中唯一标识每个对象。


0
针对您的问题。
此外,我再次寻找这个对象,即使它从Object类继承了hashCode方法,也应该返回相同的哈希码。
请查看Object#hashCode文档here 尽可能地,Object类定义的hashCode方法确实为不同的对象返回不同的整数。(通常通过将对象的内部地址转换为整数来实现,但JavaTM编程语言并不要求使用此实现技术。)
换句话说,除非您覆盖它,否则hashCode不会为两个对象返回相同的整数。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接