结论

  1. 如果两个对象相等,则hashcode()必须相等。

  2. 如果两个对象相等,a.equals(b)==b.equals(a)==true

  3. 如果两个对象有相同的hashcode值,他们也不一定是相等的。但若两个对象相等,则hashCode值一定相等。

  4. 因此若equals()被覆盖过,则hashCode()也必须被覆盖。

  5. hashCOde()的默认行为是用hash算法对在heap上的对象产生独特的值。如果没有重载过hashCode(),则该class的两个对象怎么都不会认为是相同的。

  6. equals()的默认行为(在Object类中的方法体)时执行==的比较,也就是说会测试两个引用是否是heap上同一个对象,如果equals()没有被覆盖过,两个对象永远不会认为是相同的。

为什么不同的对象会有相同的hashCode()的可能?

例:hashSet使用hashCode来达成存取,这是速度比较快的方法。重点在于hashCode()相同并不一定保证对象是相等的,因为hashCode()所使用的复杂算法也许刚好会让多个对象传回相同的杂凑值。如果HashSet在比较的时候,发现同样的hashCode值有多个对象,它会使用equals()来判断这几个对象是否真的相等。也就是说,hashCode()是用来缩小寻找范围。但最后还是要用equals()才能确认是否真的相等。

HashSet中存储元素时,先用重载过的hashCode()方法中特定算法计算出该元素的hashCode值,如果发现该集合中已经有了该hashCode值,那么调用重载过的equals()方法(比如方法体是比较该元素对象的各个属性是否相等)来进一步判断,如果相等那么说明该元素已经存在,否则说明不存在,该元素则存储在该位置上的数组上

前言

在程序设计中,有很多的“公约”,遵守约定去实现你的代码,会让你避开很多坑,这些公约是前人总结出来的设计规范。

Object类是Java中的万类之祖,其中,equals和hashCode是2个非常重要的方法。

这2个方法总是被人放在一起讨论。最近在看集合框架,为了打基础,就决定把一些细枝末节清理掉。一次性搞清楚!

下面开始剖析。

public boolean equals(Object obj)

Object类中默认的实现方式是  :   return this == obj  。那就是说,只有this 和 obj引用同一个对象,才会返回true。

而我们往往需要用equals来判断 2个对象是否等价,而非验证他们的唯一性。这样我们在实现自己的类时,就要重写equals.

按照约定,equals要满足以下规则。

自反性:  x.equals(x) 一定是true

对null:  x.equals(null) 一定是false

对称性:  x.equals(y)  和  y.equals(x)结果一致

传递性:  a 和 b equals , b 和 c  equals,那么 a 和 c也一定equals。

一致性:  在某个运行时期间,2个对象的状态的改变不会不影响equals的决策结果,那么,在这个运行时期间,无论调用多少次equals,都返回相同的结果。

一个例子

 1 class Test 2 { 3     private int num; 4     private String data; 5  6     public boolean equals(Object obj) 7     { 8         if (this == obj) 9             return true;10 11         if ((obj == null) || (obj.getClass() != this.getClass()))12             return false;13            //能执行到这里,说明obj和this同类且非null。14         Test test = (Test) obj;15         return num == test.num&& (data == test.data || (data != null && data.equals(test.data)));16     }17 18     public int hashCode()19     {20         //重写equals,也必须重写hashCode。具体后面介绍。24     }25 26 }

equals编写指导

Test类对象有2个字段,num和data,这2个字段代表了对象的状态,他们也用在equals方法中作为评判的依据。

在第8行,传入的比较对象的引用和this做比较,这样做是为了 save time ,节约执行时间,如果this 和 obj是 对同一个堆对象的引用,那么,他们一定是qeuals 的。

接着,判断obj是不是为null,如果为null,一定不equals,因为既然当前对象this能调用equals方法,那么它一定不是null,非null 和 null当然不等价。

然后,比较2个对象的运行时类,是否为同一个类。不是同一个类,则不equals。getClass返回的是 this 和obj的运行时类的引用。如果他们属于同一个类,则返回的是同一个运行时类的引用。注意,一个类也是一个对象。

1、有些程序员使用下面的第二种写法替代第一种比较运行时类的写法。应该避免这样做。

if((obj == null) || (obj.getClass() != this.getClass())) 

     return false; 

if(!(obj instanceof Test)) 

     return false; // avoid 避免!

它违反了公约中的对称原则。

例如:假设Dog扩展了Aminal类。

dog instanceof Animal      得到true

animal instanceof Dog      得到false

这就会导致

animal.equls(dog) 返回true
dog.equals(animal) 返回false

仅当Test类没有子类的时候,这样做才能保证是正确的。

2、按照第一种方法实现,那么equals只能比较同一个类的对象,不同类对象永远是false。但这并不是强制要求的。一般我们也很少需要在不同的类之间使用equals。

3、在具体比较对象的字段的时候,对于基本值类型的字段,直接用 == 来比较(注意浮点数的比较,这是一个坑)对于引用类型的字段,你可以调用他们的equals,当然,你也需要处理字段为null 的情况。对于浮点数的比较,我在看Arrays.binarySearch的源代码时,发现了如下对于浮点数的比较的技巧:

if ( Double.doubleToLongBits(d1) == Double.doubleToLongBits(d2) ) //d1 和 d2 是double类型

if(  Float.floatToIntBits(f1) == Float.floatToIntBits(f2)  )      //f1 和 f2 是d2是float类型

4、并不总是要将对象的所有字段来作为equals 的评判依据,那取决于你的业务要求。比如你要做一个家电功率统计系统,如果2个家电的功率一样,那就有足够的依据认为这2个家电对象等价了,至少在你这个业务逻辑背景下是等价的,并不关心他们的价钱啊,品牌啊,大小等其他参数。

5、最后需要注意的是,equals 方法的参数类型是Object,不要写错!

public int hashCode()

这个方法返回对象的散列码,返回值是int类型的散列码。
对象的散列码是为了更好的支持基于哈希机制的Java集合类,例如 Hashtable, HashMap, HashSet 等。

关于hashCode方法,一致的约定是:
重写了euqls方法的对象必须同时重写hashCode()方法。

如果2个对象通过equals调用后返回是true,那么这个2个对象的hashCode方法也必须返回同样的int型散列码

如果2个对象通过equals返回false,他们的hashCode返回的值允许相同。(然而,程序员必须意识到,hashCode返回独一无二的散列码,会让存储这个对象的hashtables更好地工作。)

在上面的例子中,Test类对象有2个字段,num和data,这2个字段代表了对象的状态,他们也用在equals方法中作为评判的依据。那么, 在hashCode方法中,这2个字段也要参与hash值的运算,作为hash运算的中间参数。这点很关键,这是为了遵守:2个对象equals,那么 hashCode一定相同规则。

也是说,参与equals函数的字段,也必须都参与hashCode 的计算。

合乎情理的是:同一个类中的不同对象返回不同的散列码。典型的方式就是根据对象的地址来转换为此对象的散列码,但是这种方式对于Java来说并不是唯一的要求的
的实现方式。通常也不是最好的实现方式。

相比 于 equals公认实现约定,hashCode的公约要求是很容易理解的。有2个重点是hashCode方法必须遵守的。约定的第3点,其实就是第2点的
细化,下面我们就来看看对hashCode方法的一致约定要求。

第一:在某个运行时期间,只要对象的(字段的)变化不会影响equals方法的决策结果,那么,在这个期间,无论调用多少次hashCode,都必须返回同一个散列码。
第二:通过equals调用返回true 的2个对象的hashCode一定一样。
第三:通过equasl返回false 的2个对象的散列码不需要不同,也就是他们的hashCode方法的返回值允许出现相同的情况。
总结一句话:等价的(调用equals返回true)对象必须产生相同的散列码。不等价的对象,不要求产生的散列码不相同。

hashCode编写指导

在编写hashCode时,你需要考虑的是,最终的hash是个int值,而不能溢出。不同的对象的hash码应该尽量不同,避免hash冲突。

那么如果做到呢?下面是解决方案。

1、定义一个int类型的变量 hash,初始化为 7。

接下来让你认为重要的字段(equals中衡量相等的字段)参入散列运,算每一个重要字段都会产生一个hash分量,为最终的hash值做出贡献(影响)

运算方法参考表
重要字段var的类型 他生成的hash分量
byte, char, short , int (int)var
long  (int)(var ^ (var >>> 32))
boolean var?1:0
float  Float.floatToIntBits(var)
 double  long bits = Double.doubleToLongBits(var);
分量 = (int)(bits ^ (bits >>> 32));
 引用类型   (null == var ? 0 : var.hashCode())

最后把所有的分量都总和起来,注意并不是简单的相加。选择一个倍乘的数字31,参与计算。然后不断地递归计算,直到所有的字段都参与了。

int hash = 7;

hash = 31 * hash + 字段1贡献分量;

hash = 31 * hash + 字段2贡献分量;

.....

return hash;

说明,以下的内容是我在google上找到并翻译整理的,其中加入了自己的话和一些例子,便于理解,但我能保证这并不影响整体准确性。

转载于:https://www.cnblogs.com/jobbible/p/9890657.html

再也不用担心面试官问你HashCode和equals了相关推荐

  1. 快速了解常用的非对称加密算法,再也不用担心面试官的刨根问底

    面试官:说一说你常用的加密算法有哪些? 加密算法通常被分为两种:对称加密算法和非对称加密算法.其中,对称加密算法在加密和解密时使用的密钥相同:非对称加密算法在加密和解密时使用的密钥不同,分为公钥和私钥 ...

  2. 快速了解常用的消息摘要算法,再也不用担心面试官的刨根问底

    面试官:说一说你常用的加密算法有哪些? 加密算法通常被分为两种:对称加密和非对称加密.其中,对称加密算法在加密和解密时使用的密钥相同:非对称加密算法在加密和解密时使用的密钥不同,分为公钥和私钥.此外, ...

  3. 快速了解常用的对称加密算法,再也不用担心面试官的刨根问底

    面试官:说一说你常用的加密算法有哪些? 加密算法通常被分为两种:对称加密和非对称加密.其中,对称加密算法在加密和解密时使用的密钥相同:非对称加密算法在加密和解密时使用的密钥不同,分为公钥和私钥.此外, ...

  4. 再也不用担心面试官让我用Redis实现分布式锁啦(二、Redisson实现分布式锁)

    目录 一.Jedis实现分布式锁 二.Redisson实现分布式锁(单机Redis) 一.引入依赖(3.5.7) 二.配置redis 三.配置RedisonConfig 四.提供锁接口及实现,方便统一 ...

  5. 看完华为这篇面经,我再也不用担心面试了(应届生)

    第一轮 专业面 一对一的专业面,面试官都不是HR,应该部门经理级别的人吧,所以问的问题还是侧重专业的.面试官面完一个人就出来喊另外一个人进去面试,面完好几个人之后,HR就过来给大家念名单,有些人就是叫 ...

  6. MySQL事务及实现原理全面总结,再也不用担心面试

    一. 事务transanction的四个基本要素 简单来说,事务就是要保证一组数据库操作,要么全部成功,要么全部失败,它具有以下四个基本要素. ACID:原子性(Atomicity). 一致性(Cor ...

  7. spring面试专题一点通,再也不用担心面试不会回答了

    前言 文章内容有点小长,希望你能耐心阅读,更多Java面试题以及学习资料获取方式:加Qun:1017-599-436免费获取.  还有更多包括电子书,PDF文档以及视频精讲可以分享给大家,内容覆盖很广 ...

  8. 妈妈再也不用担心别人问我是否真正用过redis了

    1. Memcache与Redis的区别 1.1. 存储方式不同 1.2. 数据支持类型 1.3. 使用底层模型不同 2. Redis支持的数据类型 3. Redis的回收策略 4. Redis小命令 ...

  9. 看完这篇,我再也不怕面试官问垃圾收集了

    看完这篇,我再也不怕面试官问垃圾收集了 说在前面:本文的篇幅较长,看本文的时候最好先去上个厕所,先准备好一杯枸杞茶,慢慢品,本文将会讲解三种垃圾收集算法:标记-清除.复制.标记-整理算法,以及各种成熟 ...

最新文章

  1. java线程wait_java多线程学习(四) 之 wait和notify
  2. python join()函数
  3. QT的Q3DBars类的使用
  4. android TextView EditTextView一些技巧使用 (视图代码布局)
  5. 从缓存行出发理解volatile变量、伪共享False sharing、disruptor
  6. Spring的@Transactional注解踩坑
  7. POJ 1007 DNA排序解题
  8. mysql 联表删除limit_sql连表删除 | 深蓝的blog
  9. inno setup读取XML文件
  10. 频遭黑客攻击的物联网,这里有妙招!
  11. C#中窗体的数据传递
  12. qca9377linux无线驱动,ubuntu下安装无线网卡去驱动Qualcomm-Atheros-QCA9377
  13. How to disable cursor positioning and text selection in an EditText? (Android)
  14. 于的繁体字有几种写法_人字繁体字有几种写法
  15. 熄风的止颤的汤治疗帕金森的优势
  16. 抖音小程序微信支付php,抖音头条小程序微信支付开通绑定最全教程!
  17. 本科计算机考研考英语,2016考研必需知道的10件事
  18. ltib常用命令详解
  19. python实现图片切割和合并
  20. 华三模拟器:实现路由器接口使用dhcp获取地址

热门文章

  1. 双系统引导修复与引导项删除
  2. C++11库中 steady_clock , system_clock和high_resolution_clock的区别
  3. 用最少数量的箭引爆气球
  4. sift+图像匹配 算法
  5. 记录 之 Argparse 中的 可选参数 action 用法
  6. Ubuntu apt 卸载详解
  7. mysql字段说明_mysql 字段类型说明
  8. HBase常用的Shell命令
  9. 计组-CISC/RISC小节
  10. TCP传输-出现差错的解决办法