最近在面试的时候,当问完了HashMap的数据结构之后,通常会再多问一个问题,就是:重写equals方法时通常为什么也要重写一下hashcode方法?

其实这个问题,本质上又回到HashMap的应用场景了,就是想看一下面试者是否真的融会贯通。今天这篇文章就带大家了解一下equals方法和hashcode方法之间的关系,以及相关的知识点。

equals与hashcode的存在

其实每个类都有一个equals方法和hashcode方法。因为所有的类都继承自Object类。Object类中定义如下:

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

直观上可以看到equals方法默认比较的是对象的引用,直接用“==”进行比较。而hashCode方法是一个native方法,返回值为整型。

而这两个方法都未被final修饰,都是可以进行重写的。

对于我们经常使用的比如String 、Math、Integer、Double等类,都进行了equals()和hashCode()方法的重写。

equals()方法

equals()方法是用来判断两个对象是否相等。Object默认实现了equals方法,但很明显不太符合个性化的需求,因此往往需要进行重写。比如常用的String类,重写的equals方法如下:

// 重写equals方法
public boolean equals(Object anObject) {if (this == anObject) {return true;}if (anObject instanceof String) {String anotherString = (String)anObject;int n = value.length;if (n == anotherString.value.length) {char v1[] = value;char v2[] = anotherString.value;int i = 0;while (n-- != 0) {if (v1[i] != v2[i])return false;i++;}return true;}}return false;
}

这里的比较已不再是单纯的地址比较了。首先通过地址进行比较,如果地址相同那么肯定是相同的对象。如果地址不同就再拿char数组的内容进行比较,完全相等则返回true。

equals()方法的特质

在Object类的equals方法上有注释说明了equals()方法需满足的一些特性:

  • 自反性(reflexive)。对于任意不为null的引用值x,x.equals(x)一定是true;
  • 对称性(symmetric)。对于任意不为null的引用值x和y,当且仅当x.equals(y)是true时,y.equals(x)也是true;
  • 传递性(transitive)。对于任意不为null的引用值x、y和z,如果x.equals(y)是true,同时y.equals(z)是true,那么x.equals(z)一定是true;
  • 一致性(consistent)。对于任意不为null的引用值x和y,如果用于equals比较的对象信息没有被修改的话,多次调用时x.equals(y)要么一致地返回true要么一致地返回false;
  • 对于任意不为null的引用值x,x.equals(null)返回false;

对照上面特质,我们发现Object方法直接比较的是两个引用地址,只有两个地址相同才相等,也就是说是差别可能性最大的等价关系。

而String的equals方法,不仅包含应用地址相同这种情况,还包括里面所存储的字符串值相同的情况。也就是说虽然是两个String对象,但是它们的字符串值相等,那么equals方法返回的结果就是true。这也正是大多数情况下我们所说的“equals方法比较的是值”。

由于Object的equals方法的默认特例存在,因此在没有自定义equals方法时,我们不能一概的说equals方法比较的是具体的值,而“==”比较的是引用。

hashCode()方法

hashCode()方法返回对象的一个hash code值。该方法被用于hash tables,如HashSet、HashMap。

hashCode()是一个native方法,返回值类型是整形,并且可以被重写。

Object中的native hashCode()方法将对象在内存中的地址作为哈希码返回,可以保证不同对象的返回值不同。

还以String类为例,它的hashCode方法为:

// 重写hashCode方法
public int hashCode() {int h = hash;if (h == 0 && value.length > 0) {char val[] = value;for (int i = 0; i < value.length; i++) {h = 31 * h + val[i];}hash = h;}return h;
}

上述hash值的计算注释中有说明,基本公式为:s[0]*31^(n-1) + s[1]*31^(n-2) + … + s[n-1]。

其中, s[i]是字符串的第i个字符,n是字符串的长度,^表示求幂(空字符串的哈希码为0)。

计算过程中使用数字31,主要有以下原因:

1、由于质数的特性,它与其他数字相乘之后,计算结果唯一的概率更大,哈希冲突的概率更小。

2、使用的质数越大,哈希冲突的概率越小,但是计算的速度也越慢;31是哈希冲突和性能的折中,实际上是实验观测的结果。

3、JVM会自动对31进行优化:31 * i == (i << 5) - i;

hashCode()方法的作用

前面提到hashCode()方法主要用于hash表中,比如HashSet、HashMap等。

我们先来看一下ArrayList,它的底层是数组,每个数据往底层的数组中存取即可,数据不需要判断是否重复。

集合Set中的元素是无序不可重复的,那么如何确保存入的元素不重复呢?逐个调用equals()方法进行比较?数据量少的时候还可以,但数据量大了时间复杂度基本上是O(n),会出现性能问题。

Java中采用哈希算法来解决这个问题,将对象(或数据)依特定算法直接映射到一个地址上,这样时间复杂度趋于O(1),对象的存取效率大大提高。

集合Set添加某元素时,先调用hashCode()方法,定位到此元素实际存储位置,如果这个位置没有元素,说明是第一次存储;若此位置有对象存在,调用equals()进行比较,相等就舍弃此元素不存,不等则散列到其他地址。

上面的示例也说明了为什么equals()相等,则hashCode()必须相等,进而当重写了equals方法,也要对hashCode()方法进行重写。

HashMap的基本处理机制与HashSet很类似,只不过底层的数据存储结构有所不同而已。

简而言之,在集合查找时,hashcode能极大的降低对象比较次数,提高查找效率。

hashCode()方法的性质

hashCode的实现也有一定的要求,相关英文说明在Object的equals方法注解上:

  • 在一个Java应用的执行期间,如果一个对象提供给equals做比较的信息没有被修改的话,该对象多次调用hashCode()方法,该方法必须始终如一返回同一个integer。
  • 如果两个对象根据equals(Object)方法是相等的,那么调用二者各自的hashCode()方法必须产生同一个integer结果。
  • 并不要求根据equals(java.lang.Object)方法不相等的两个对象,调用二者各自的hashCode()方法必须产生不同的integer结果。但对于不同的对象产生不同的integer结果,有可能会提高hash table的性能。

如何重写hashCode()

《Effective Java》中提供了一种简单通用的hashCode算法。

A、初始化一个整形变量,为此变量赋予一个非零的常数值,比如int result = 17;

B、选取equals方法中用于比较的所有域(之所以只选择equals()中使用的域,是为了保证上述原则的第1条),然后针对每个域的属性进行计算:

  • (1) 如果是boolean值,则计算f ? 1:0
  • (2) 如果是byte\char\short\int,则计算(int)f
  • (3) 如果是long值,则计算(int)(f ^ (f >>> 32))
  • (4) 如果是float值,则计算Float.floatToIntBits(f)
  • (5) 如果是double值,则计算Double.doubleToLongBits(f),然后返回的结果是long,再用规则(3)去处理long,得到int
  • (6) 如果是对象应用,如果equals方法中采取递归调用的比较方式,那么hashCode中同样采取递归调用hashCode的方式。否则需要为这个域计算一个范式,比如当这个域的值为null的时候,那么hashCode 值为0
  • (7) 如果是数组,那么需要为每个元素当做单独的域来处理。java.util.Arrays.hashCode方法包含了8种基本类型数组和引用数组的hashCode计算,算法同上。

C、最后,把每个域的散列码合并到对象的哈希码中。

小结

关于equals方法很明确的是用于比较两个对象是否相等。而对于hashCode方法重点是为了在类似HashMap场景下提升效率,只算是技术要求。

在集合中通常通过equals方法来比较对象是否相等,通过hashCode方法来解决大数据量时会发生的性能问题。

在实践中我们很少使用Object对象来作为Map的key,也是因为如果Object对象的属性变了,会导致hashCode变化,进而可能会导致找不到对应值,而String是不可变的对象,作为key就很适合。

原文链接:《面试题:重写equals方法为什么通常会重写hashcode方法?》


程序新视界
公众号“程序新视界”,一个让你软实力、硬技术同步提升的平台,提供海量资料

面试题:重写equals方法为什么通常会重写hashcode方法?相关推荐

  1. 重写equals()时为什么也得重写hashCode()之深度解读equals方法与hashCode方法渊源

    重写equals()时为什么也得重写hashCode()之深度解读equals方法与hashCode方法渊源 在使用Map接口时,我们的愿望是当key1.equals(key2)时,它们获取的valu ...

  2. 面试官:重写 equals 时为什么一定要重写 hashCode?

    作者 | 磊哥 来源 | Java面试真题解析(ID:aimianshi666) 转载请联系授权(微信ID:GG_Stone) 重要说明:本篇为博主<面试题精选-基础篇>系列中的一篇,关注 ...

  3. JavaSE——为什么重写equals的同时一定要重写hashCode?

    文章目录: 1.引出话题 2.关于equals方法 3.关于hashCode方法 4.为什么要一起重写这两个方法? 5.总结 1.引出话题 equals 方法和 hashCode 方法是 Object ...

  4. java重写面试题_Java面试题:重写了equals方法,为什么还要重写hashCode方法?

    核心问题:重写了equals方法,为什么还要重写hashCode方法? 这不仅仅是一道面试题,而且是关系到我们的代码是否健壮和正确的问题.在前面两篇文章涉及到了equals方法的底层讲解:<说说 ...

  5. 为什么重写equals一定要重写hashCode方法?

    大家都知道,equals和hashcode是java.lang.Object类的两个重要的方法,在实际应用中常常需要重写这两个方法,但至于为什么重写这两个方法很多人都搞不明白. 下面我们看下Objec ...

  6. Java重写equals方法时为什么要重写hashCode方法

    在我们平时编写Java代码时,重写equals方法时一定要重写hashCode方法,这是为什么呢? 在讨论这个问题前,我们先看下Object类中hashCode方法和equals方法. hashCod ...

  7. 为什么要重写hashCode()方法和equals()方法以及如何进行重写

    一.前言 本篇文章主要探讨的问题有三个: 1.首先我们为什么需要重写hashCode()方法和equals()方法? 2.在什么情况下需要重写hashCode()方法和equals()方法? 3.如何 ...

  8. 为什么使用HashMap需要重写hashcode和equals方法_不同时重写equals和hashCode又会怎样?听听过来人的经验...

    可能一问到equals和hashCode相关的问题,就会有人讲他们的自反性,对称性,一致性,传递性等几条约定了,此时我不得不佩服,这么多约定竟然都能记得,但我不知道你是不是真的理解呢. 一.我不同时重 ...

  9. 重写equals()与hashCode()方法

    出自:http://blog.csdn.net/renfufei/article/details/16339351 Java语言是完全面向对象的,在java中,所有的对象都是继承于Object类.Oj ...

最新文章

  1. Springboot中优雅进行字段校验
  2. Σ-delta ADC是否可以测量热噪声?
  3. Android下NDK开发环境搭建
  4. Ardino基础教程 16_一位数码管
  5. 不包含本位置值的累乘数组
  6. MyEclipse 中修改JSP模板
  7. python带界面的计算器
  8. 获取32R的图像的直方图的一个算法
  9. 看看老外是如何理解抽象类的
  10. 单元测试怎么测试线程_单元测试线程代码的5个技巧
  11. c#中属性自动完成快捷键
  12. python编程词典_Python 字典(Dictionary)操作详解
  13. 如何成为嵌入式软件工程师_成为高效软件工程师的三个关键
  14. 原来大家最拼命的时候是这样子的。
  15. 两数求和(C语言超简单解法)
  16. 虚拟机全屏后隐藏vmware菜单栏的问题
  17. 离职通知邮件主题写什么好_辞职邮件怎么写,需要包含哪些内容
  18. 计算机RAM ,ROM,NOR FLASH ,NAND FLASH和手机RAM,ROM大家庭血缘关系图
  19. html2canvas.js 截屏微信头像不显示
  20. 打印程序在计算机上的应用程序,修复无法在计算机上运行的后台打印程序服务的操作步骤...

热门文章

  1. 关于u盘快捷方式病毒的解决方案
  2. 给女朋友的微信专属推送
  3. 变异系数在线计算机,数理统计在线计算器
  4. js判断ipad pro设备
  5. 使用Scanner收集你的身高体重,并计算出你的BMI值是多少 BMI的计算公式是 体重(kg) / (身高*身高)
  6. Web前端不同阶段工资待遇如何?前端开发真的很值钱吗?
  7. 面向对象编程 面向过程编程_面向对象的编程真的是死定了
  8. android 平板 旗舰,聊一款旗舰级安卓平板电脑
  9. 《测量助理》最新版本V3.0.220618发布更新
  10. 淘宝服务商怎么入驻(入驻淘宝需要什么条件)