重写了equals()为什么必须重写hashcode()

本文详细解析了HashMap的put过程和HashSet的实现原理,强调了为何在自定义类中重写equals()时必须重写hashcode()。通过举例说明,如果不重写hashcode(),可能导致HashMap中存在相同属性但不同引用的对象,违反数据结构设计。重写这两个方法遵循了数据结构设计规范,确保了对象在数据结构中的正确比较和存储。
摘要由CSDN通过智能技术生成

要彻底理解重写equals()为什么必须重写hashcode()方法,就需要明白HashMap的put过程以及什么是Hash冲突。

        首先我们来说一下HashSet与HashMap:

         我们可以从源码中知道:

        HashSet的实现其实就是HashMap的key,其所有的Value是PRESENT(源码中写死的new的一个Object对象),而我们往HashSet添加值的时候(add()),其实是给底层HashMap添加值,源码如下:

public boolean add(E e) {
    return map.put(e, PRESENT)==null;
}

         map.put(e,PRESENT)中的map跟PRESENT在第一张图中其实都已经标出来了。

        

        接下来我们探讨HashMap的底层结构以及put原理

        HashMap的底层是以数组加链表实现的,附上到网上找的一张图:

                

HashMap是数组加链表的形式存储数据的,数组的初始长度是16,HashMap自带扩容机制,HashMap的容量(我的理解是数组长度)范围是在16(初始化默认值)~2 ^ 30;

扩容机制:每次长度增加一倍,初始化长度是16,这也解释了为什么HashMap的长度总是2的次方;

        HashMap的put()过程:

         这个问题跟为什么重写hashCode()息息相关,重点:

        如果往HashMap中put()一个key-value,首先根据key的hashCode()方法计算出一个hashCode值,这个值再通过hash()方法,计算得出这个key-value应该放在数组中的哪个位置(即数组下标),这也是我所理解的再哈希法(如果有人去了解如何解决hash冲突的时候应该会明白这个字眼),找到数组下标后,我们知道这个数组里面的每一个元素都是一个链表,这个时候就采用key的equals()方法去此链表中一个个去比对,看链表中是否有相同的key,如果有相同的key,则覆盖,如果没有相同的key,则在链表末尾添加;

        上面就是我所理解的HashMap的put()的大概过程,可能会有一些地方有遗漏;

        为什么不直接用hashcode值直接作为数组下标:因为hashCode值最大有2的31次方,数组好像没有这么大长度,hashCode()方法返回的是int整数类型,其范围为-(2 ^ 31)~(2 ^ 31 - 1),约有40亿个映射空间,而HashMap的容量范围是在16(初始化默认值)~2 ^ 30,HashMap通常情况下是取不到最大值的,并且设备上也难以提供这么多的存储空间,从而导致通过hashCode()计算出的哈希值可能不在数组大小范围内,进而无法匹配存储位置(数组下标);

        hashCode()与equals()

        hashCode()跟equals()都是都是从超级父类Object中继承而来的方法,在Object中他们分别是:

        

public native int hashCode();
public boolean equals(Object obj) {
    return (this == obj);
}

        hashCode()方法不重写的话,是用native修饰的,也就是默认由操作系统实现,即hashcode值默认是由通过内存中的引用计算得出的;

        equals()方法不重写的话,底层是用"=="来判断是否相等,"=="是判断引用是否相等;

        重写了equals()为什么必须重写hashcode()

        举个例子:

        有一个类(定位A)有两个属性:String name,int id

        现在我们new了两个对象,都是new A(srg,123);也就是说这两个对象的属性值是相等的,但是明显这两个对象的引用不相等。

        通常针对大部分场景时,我们只要确定他们的属性值相等,就认为他们相等。

        现在我们在A类中重写了equals()方法,我们重写equals()的判断规则如下:

public boolean equals(A a) {
    if(this.name.equals(a.name) && this.id==a.id){
        return true;
    }else{
        return false;
    }
}

        name跟id是String跟int类型的,int是基础数据类型,==是用来判断值是否相等;String是引用类型,但是String类Java已经帮我们重写了equals()方法,也是判断值相等。

(注意:==对于值类型判断值,对于引用类型判断引用)

        所以我们刚刚重写equals()方法的规则是只要A类对象的值相等,调用equals()就返回true,否则返回false。

        但是我们new的两个对象new A(srg,123),虽然他们equals()为true,但是明显它们hashCode值不相等(上面已经说过:因为引用不一样,而hashCode()是操作系统根据引用来计算的);

        

        理解了上述的东西的话,我们又回到HashMap的put()过程,我们首先通过hashCode()方法计算下标,上面new的两个对象new A(srg,123),在A类中没有重写hashCode方法的话,如果把这个两个对象作为HashMap中的key,是不符合HashMap的结构设计的(key不能重复);

        为什么呢?因为通过计算hashCode()跟hash()计算,这个两个对象如果作为key的话,他们在数组中的下标是不一样的,假设A1对象在下标1中,A2对象在下标23中,A1首先put()进去,然后在A2对象put()时,它到下标23中与其链表里面的每一个元素通过equals()方法判断是否相等,发现没有相等的,然后也put()进去了。

        这个时候,此HashMap中有两个key一样的key-value,分别在下标1中的A1,以及下标23中的A2,很明显,这不符合我们设计的HashMap的初衷。

        所以当A类对象只重写了equals()方法,而没有重写hashCode()方法的话,此对象不能作为HashMap的Key,也不能放入Set集合里,大家也可以仔细想想,那你设计的这个数据结构还能做什么。

        通常我们一般以String或者Integer等作为HashMap的key,因为他们已经都重写equals()跟hashCode()方法;

        

        最终总结:重写了equals()为什么必须重写hashcode(),这只是一种数据结构设计的规范,我们都应该遵守这种规范。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

SRG仁港

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值