equals和hashCode的理解

引言“作为Java程序员,你是否真的懂了equals()和hashCode()两个方法呢?

为什么Object需要hashCode()这个方法?用意何在?

学过数据结构的都知道,哈希表或散列表是一种查询速度比较快的数据结构。时间复杂度O(1)。在Java语言中,类Object中有一方法hashCode()

但是,很少有人思考为什么?如果说两个数进行比较需要equals()方法这个是合情合理的,但是为啥需要hashCode()呢?假如说当数据量很大时,比如10万条数据或者更多的数据,如果采用equals方法去逐一比较,效率必然是一个问题,尤其是对性能要求严格的系统。这个时候hashCode()能够减少equals()的比较次数,大大提升效率。我觉得Object类设计hashCode()来提升比较的效率。还需要记住一点,hashCode是为了提高在散列数据结构存储中查找的效率,而在线性表中是不起作用的

为什么重写equals()方法必须重写hashCode()方法?

上面是equals()的源码,看注释知道它有如下的一些特性

自反性:对于任意的引用值x,x.equals(x)一定为true

对称性:对于任意的引用值x 和 y,当x.equals(y)返回true,y.equals(x)也一定返回true.

传递性:对于任意的引用值x、y和z,如果x.equals(y)返回true,并且y.equals(z)也返回true,那么x.equals(z)也一定返 回 true

一致性:对于任意的引用值x 和 y,如果用于equals比较的对象信息没有被修改, 多次调用x.equals(y)要么一致地返回true,要么一致地返回false

非空性:对于任意不是null的x,x.equals(null)一定返回false

默认的equals()只是比较对象的地址,即Object提供的equals()方法比较的结果和==运算符比较的结果完全相同。所以,实际应用中,常常要重写equal方法,这时还要注意以下两点:

1.重写的修饰符必须是public,因为重写的是Object的方法

2.重写的参数类型必须是Object。

有时候在想,是不是可以直接根据hashCode()值判断两个对象是否相等吗?答案是否定的,因为不同的对象可能会生成相同的hashcode值。虽然不能根据hashcode值判断两个对象是否相等,但是可以直接根据hashcode值判断两个对象不等,如果两个对象的hashcode值不等,则必定是两个不同的对象。如果要判断两个对象是否真正相等,必须通过equals方法。也就是说对于两个对象,

如果调用equals方法得到的结果为true,则两个对象的hashcode值必定相等;

如果equals方法得到的结果为false,则两个对象的hashcode值不一定不同;

如果两个对象的hashcode值不等,则equals方法得到的结果必定为false;

如果两个对象的hashcode值相等,则equals方法得到的结果未知。

也有说,并不是必须的,得看具体的情况

1 当equals方法返回的结果和使用等号比较的结果是一致的时候,是没有必要重写hashCode方法。

当用等号比较对象,只有是内存中同一个对象实例,才会返回true,当然调用其hashCode()方法肯定返回相同的值,这满足了满足了hashCode的约束条件,所以不用重写hashCode()方法。

2 当equals方法返回的结果和使用等号比较的结果是不一致的时候,就需要重写hashCode方法。

当重写后的equals方法不认为只有是在内存中同一个对象实例,才返回true,如果不重新hashCode方法()(Object的hashCode()方法 是对内存地址的映射),hashCode方法返回的值肯定是不同的,这违背了hashCode的约束条件,所以必须要重新hashCode方法,并满足对hashCode的约束条件。

我觉得,对于重写equals方法时必须重写hashCode()方法,简单省事。

为什么hashCode()方法实现用的31这个质数?

我们可以查看一下String类的hashCode()源码,发现有如下的一个公式:

s[0]31^(n-1) + s[1]31^(n-2) + … + s[n-1]

那么,为什么是31呢?大概有如下原因:

31是一个素数,素数作用就是如果我用一个数字来乘以这个素数,那么最终的出来的结果只能被素数本身和被乘数还有1来整除!。(减少冲突)

31可以 由i*31== (i

选择系数的时候要选择尽量大的系数。因为如果计算出来的hash地址越大,所谓的“冲突”就越少,查找起来效率也会提高。(减少冲突,具体哈希冲突的解决策略大家看看网上查查)

并且31只占用5 bits,相乘造成数据溢出的概率较小。

为什么hashCode()依赖于对象中易变的数据时需要当心?

对于依赖对象的易变数据,当然需要当心,不然你会出错的。在Effactive java中有如下一段话:

在程序执行期间,只要equals方法的比较操作用到的信息没有被修改,那么对这同一个对象调用多次,hashCode方法必须始终如一地返回同一个整数。

如果两个对象根据equals方法比较是相等的,那么调用两个对象的hashCode方法必须返回相同的整数结果。

如果两个对象根据equals方法比较是不等的,则hashCode方法不一定得返回不同的整数

在Java编程思想中同样有一句话:

“设计hashCode()时最重要的因素就是:无论何时,对同一个对象调用hashCode()都应该产生同样的值。如果将一个对象用put()添加进HashMap时产生一个hashCdoe值,而用get()取出时却产生了另一个hashCode值,那么就无法获取该对象了。所以如果你的hashCode方法依赖于对象中易变的数据,用户就要当心了,因为此数据发生变化时,hashCode()方法就会生成一个不同的散列码”。

在设计hashCode方法和equals方法的时候,如果对象中的数据易变,则最好在equals方法和hashCode方法中不要依赖于该字段

下面给出一个示例:

参考文章

1浅谈Java中的hashcode方法 海子 http://www.cnblogs.com/dolphin0520/p/3681042.html

2 如何重写hashCode()和equals()方法 王鸿飞 https://blog.csdn.net/neosmith/article/details/17068365

3为什么在定义hashcode时要使用31这个数呢? steveguoshao https://blog.csdn.net/steveguoshao/article/details/12576849

4 为什么在定义hashcode时要使用31这个数呢?小M的专栏 https://blog.csdn.net/mingli198611/article/details/10062791

5 Java之音 http://www.javazhiyin.com/?p=513

6 深入理解equals()和hashCode() http://www.cnblogs.com/lion88/p/5435789.html

  • 发表于:
  • 原文链接https://kuaibao.qq.com/s/20180626G1K6HY00?refer=cp_1026
  • 腾讯「云+社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。

扫码关注云+社区

领取腾讯云代金券