为什么equals和hashCode方法需要同时重写

注意:如果是在写算法题的话,假设只涉及到比较两个对象是否相等,用不到hashCode的话,是可以不用同时重写的。当然,这样并不建议。

一、原生equals和hashCode

1.原生的equals方法用于严格判断一个对象是否相等

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

2.原生的hashCode方法返回根据内存地址换算出来的一个值

  • As far as is reasonably practical, the {@code hashCode} method defined

    by class {@code Object} returns distinct integers for distinct objects.

二、为什么需要重写equals

在实际业务中,我们判断对象是否相等往往并不是从严格意义上的角度出发,比如判断两个Product对象是否相等,我们会以成员变量name判断其是否相等,此时就需要重写equals

public boolean equals(Object obj) {
	  if(obj==null)
		  return false;
	  if(!(obj instanceof Product))
		  return false;
	  if(this==obj)
		  return true;
	  
	  Product pro = (Product)obj;
	  if(this.name.equals(pro.name))
		  return true;
	  return false;
	}

三、重写了equals,为何要重写hashCode

  1. 如果两个对象根据equals(Object)方法是相等的,那么调用这两个对象中任一个对象的hashCode方法必须产生同样的整数结果。

  2. 如果两个对象根据equals(Object)方法是不相等的,那么调用这两个对象中任一个对象的hashCode方法,不要求必须产生不同的整数结果。然而,程序员应该意识到这样的事实,对于不相等的对象产生截然不同的整数结果,有可能提高散列表(hash table)的性能。


    ------------------- 引自《Effective Java》中有关Object.hashCode的通用约定

根据上述第一条:相等的对象必须具有相等的散列码,即hashCode。

反之,若不重写hashCode,原生的方法通过对象地址产生散列码,不会相等。

四、HashMap和HashSet中的HashCode

1.HashMap中的hash方法:
 static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }

上面代码里的key.hashCode()函数调用的是key键值类型自带的哈希函数。

若key为自定义类,则调用自定义类中的hashCode函数。

2.HashMap的底层处理机制:

HashMap的底层处理机制是以数组的方法保存放入的数据的(Node<K,V>[] table),其中的关键是数组下标的处理。

数组的下标是根据传入的元素hashCode方法的返回值再和特定的值异或决定的。

3.HashSet是通过HasMap来实现的:

HashMap的输入参数有Key、Value两个组成,在实现HashSet的时候,保持HashMap的Value为常量,相当于在HashMap中只对Key对象进行处理。

4.如果不重写hashCode:

若不重写hashCode可能导致HashSet、HashMap不能正常的运作。

另外,如果我们将某个自定义对象存到HashMap或者HashSet及其类似实现类中的时候, 如果该对象的属性参与了hashCode的计算,那么就不能轻易修改该对象参数hashCode计算的属性了

有可能会移除不了元素,导致内存泄漏。

五、Java源码中的hashCode

以下是StringUTF16类中的hashcode方法,直接作用于String类

 public static int hashCode(byte[] value) {
        int h = 0;
        int length = value.length >> 1;//右移一位,相当于 /2
        for (int i = 0; i < length; i++) {
            h = 31 * h + getChar(value, i);//神奇的数字31
        }
        return h;
    }

注:为何用31——任何数n*31都可以被jvm优化为(n<<5)-n,移位和减法的操作效率比乘法的操作效率高很多

17、31、33、63、127和129等相对其他的奇数的一个很明显的优势是,由于这些奇数与16、32、64、128只相差1,可以通过移位(如1 << 4 = 16)和加减1来代替乘法,速度更快。

​ -------------------------------------------------------- 引自他人博客(其中还有实验验证)

六、自定义类如何重写hashCode

要重写自己的hashCode方法并没有什么绝对正确的答案,但是我们的目标是:不相等的对象尽可能有不同的hashCode,而且必须满足的一个通用约定是:相等的对象应该具有相同的hashCode。下面介绍一种hashCode的实现方式,这种实现方式对一般的程序来说足够了,至于如何实现更完美的hashCode方法就留给数学家或者理论家去讨论吧。

以下为
《Effective Java》中提出的一种简单通用的hashCode算法

第一步:定义一个初始值,一般来说取17

int result = 17;

第二步:分别解析自定义类中与equals方法相关的字段(假如hashCode中考虑的字段在equals方法中没有考虑,则两个equals的对象就很可能具有不同的hashCode)

情况一:字段a类型为boolean 则[hashCode] = a ? 1 : 0;

情况二:字段b类型为byte/short/int/char, 则[hashCode] = (int)b;

情况三:字段c类型为long, 则[hashCode] = (int) (c ^ c>>>32);

情况四:字段d类型为float, 则[hashCode] = d.hashCode()(内部调用的是Float.hashCode(d), 而该静态方法内部调用的另一个静态方法是Float.floatToIntBits(d))

情况五:字段e类型为double, 则[hashCode] = e.hashCode()(内部调用的是Double.hashCode(e), 而该静态方法内部调用的另一个静态方法是Double.doubleToLongBits(e),得到一个long类型的值之后,跟情况三进行类似的操作,得到一个int类型的值)

情况六:引用类型,若为null则hashCode为0,否则递归调用该引用类型的hashCode方法。

情况七:数组类型。(要获取数组类型的hashCode,可采用如下方法:s[0]*31 ^ (n-1) + s[1] * 31 ^ (n-2) + … + s[n-1], 该方法正是String类的hashCode实现所采用的算法)

第三步:对于涉及到的各个字段,采用第二步中的方式,将其依次应用于下式:

result = result * 31 + [hashCode];

补充说明一点:如果初始值result不取17而取0的话,则对于hashCode为0的字段来说就没有区分度了,这样更容易产生冲突。比如两个自定义类中,一个类比另一个类多出来一个或者几个字段,其余字段全部一样,分别new出来2个对象,这2个对象共有的字段的值全是一样的,而对于多来的那些字段的值正好都是0,并且在计算hashCode时这些多出来的字段又是最先计算的,这样的话,则这两个对象的hashCode就会产生冲突。还是那句话,hashCode方法的实现没有最好,只有更好。

​ ---------------------------------------------------------------------------------------引自他人博客

七、总结:equals和hashCode判断元素相等

1、hashcode相等,两个对象不一定相等,需要通过equals方法进一步判断;

2、hashcode不相等,两个对象一定不相等;

3、equals方法为true,则hashcode肯定一样;

4、equals方法为false,则hashcode不一定不一样;

理解为主,记忆为辅

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
哈希码内存泄漏是指当一个对象被存储进哈希集合后,如果该对象中参与计算哈希码的属性被修改,就会导致内存泄漏问题。具体来说,当对象的哈希码发生变化时,即使在使用该对象的引用作为参数去哈希集合中检索对象,也无法找到该对象,从而无法从哈希集合中删除该对象,导致内存中仍然存在该对象,造成内存泄漏。这是因为哈希集合在存储对象时是根据对象的哈希码来确定存储位置的,如果哈希码发生变化,就无法正确地找到对象。为了避免哈希码内存泄漏问题,应该在对象被存储进哈希集合之后,不再修改参与计算哈希码的属性。\[1\]\[2\]\[3\] #### 引用[.reference_title] - *1* [hashCode方法作用 内存泄漏问题](https://blog.csdn.net/snbing07/article/details/38456387)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* *3* [分析 equalshashCode 与内存泄露](https://blog.csdn.net/m0_47422889/article/details/119079853)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值