【面试题】为什么重写equals方法就一定要重写hashCode方法?

文章讲述了hashCode方法的常规协定,包括其一致性要求,并详细解释了如何影响散列集合如HashSet和HashMap的元素添加。着重讨论了重写hashCode和equals方法对结果的影响,强调两者必须匹配以确保正确性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

hashCode 的常规协定

查看官方文档,可以看到 hashCode 的常规协定是:

  • 在 Java 应用程序执行期间,在对同一对象多次调用 hashCode 方法时,必须一致地返回相同的整数,前提是将对象进行 equals比较时所用的信息没有被修改。从某一应用程序的一次执行到同一应用程序的另一次执行,该整数无需保持一致。
  • 如果根据 equals(Object) 方法,两个对象是相等的,那么对这两个对象中的每个对象调用 hashCode 方法都必须相同的整数结果
  • 如果根据 equals(java.lang.Object) 方法,两个对象不相等,那么对这两个对象中的任一对象上调用 hashCode 方法 要求 一定生成不同的整数结果(存在哈希冲突)。

原生hashCode、equals方法

  • Java 里面任何一个对象都有一个 nativehashCode() 方法,默认是JVM使用随机数来生成的(满足上面的协议)。
    在这里插入图片描述
  • 在Java中,如果我们没有自定义equals方法,那么默认的equals方法就是比较两个对象的 地址是否相同
    在这里插入图片描述

散列集合的重复性校验

散列集合(HashSet、HashMap等等)添加元素会有重复性校验,依据的就是上面的协议,具体校验的方式如下:
先使用对象的hashCode的值进行取模运算,判断是否相等:

  • 不同,将直接判定 Object 不同,跳过equals方法的检验,这加快了冲突处理效率。
  • 相同,这个位置通过哈希算法可能存在多个元素(存在哈希冲突),这个时候即需要通过 equals方法比较(极大缩小比较范围,高效判断),最终判定该存储结构中是否有重复元素。

举例说明:

为了更好地理解这个问题,这里举一个具体的例子:
定义一个学生类,并在main方法中创建两个对象s1和s2,将s1加入到map中,然后通过s2获取值。

public class Student {
    private Integer id;
    public Student(Integer id) {
        this.id = id;    
    }
    
    
    public static void main(String[] args) {
        Student s1 = new Student(1);
        Student s2 = new Student(2);
        Map<Student,String> map = new HashMap<>();
        map.put(s1, "Hello World!");
        System.out.println(map.get(s2)));
    }
}

1. 假设没有重写hashCode方法

native的hashCode()方法由于s1和s2是两个不同的对象,对应的 地址值不同,根据hashCode计算出的 哈希值不同,它们将散列在哈希表中的不同位置。所以就无法通过s2获取s1对应的值。

输出结果为 null

2. 如果重写了hashCode方法

@Override
public int hashCode() {
    return Objects.hash(id);
}

此时 s1 和 s2 都是通过调用属性 id 的 hashCode 方法来计算哈希值,由于Integer类中已经重写了hashCode方法(见下),s1.hashCode() == s2.hashCode ,也就是说它们都散列在哈希表的同个位置。
但是在哈希式的存储结构中存在哈希冲突,因此还需要使用equals方法进一步判断。

/**
 * Returns a hash code for this <code>Integer</code>.
 *
 * @return  a hash code value for this object, equal to the 
 *          primitive <code>int</code> value represented by this 
 *          <code>Integer</code> object. 
 */
public int hashCode() {
    return value;
}

3. 假设重写了hashCode方法,但是没有重写equals方法:

在java中,map集合底层是通过 数组+链表+红黑树 实现的(即:使用“拉链法”解决哈希冲突)。
由于重写 hashCode 方法后,s1和s2计算出的哈希值相同(哈希冲突),就会继续使用equals方法,判断对应链表中是否存在s2元素。
由于没有重写 equals 方法,equals方法默认还是通过比较内存地址 判断两个元素是否相同,由于s1和s2的地址值不同,s1.equals(s2) == false ,因此还是无法获取s1对应的值。

输出结果仍然是null

4. 如果重写了hashCode方法,并且重写了equals方法:

@Override
public boolean equals(Object o) {
    if (this == o) return true;
    if (o == null || getClass() != o.getClass()) return false;
    Student student = (Student) o;
    return Objects.equals(id, student.id);
}

s1和s2是通过调用 id 的equals方法来比较是否相同,由于Integer内部已经重写了equals方法(如下),s1.equals(s2) == true ,可以通过s2获取s1对应的值。

/**
 * Integer源码
 * A.equals(B) 比较两个对象A和B。
 * 当且仅当B非空,而且B是个值和A的值相等的Integer对象时,A和B相等。 
 */
public boolean equals(Object obj) {
    if (obj instanceof Integer) {
        return value == ((Integer)obj).intValue();
    }
    return false;
}

输出结果为:Hello World!


问题回答

如果类中重写了 equals 方法,但没有重写 hashCode 方法,会出现创建出的两个对象,a.equals(b) 这个表达式成立,但是生成的哈希值不同的悖论 —— 由于散列集合是通过 hashCode 计算 key 的存储位置,也就是说这两个完全相同的对象,却存储在哈希表中的两个位置,与协议相违背,程序会出现不可预料的错误。因此重写 equals 方法就一定要重写 hashCode 方法。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值