在每个覆盖equals方法的类中,也必须覆盖hashCode方法。否则,会违反Object.hashCode的通用约定,从而导致该类无法结合所有基于散列的集合一起正常工作,包括HashMap,HashSet,Hashtbale。

hashCode约定内容:

1.只要对象equals方法的比较操作所用到的信息没有被修改,对同一对象调用多次,hashCode方法都必须返回同一整数。在同一应用程序的多次执行过程中,每次执行返回的整数可以不一致。

2.如果两个对象根据equals(Object)方法比较是相等的,那么这两个对象的hashCode返回值相同。

3.如果两个对象根据equals(Object)方法比较是不等的,那么这两个对象的hashCode返回值不一定不等,但是给不同的对象产生截然不同的整数结果,能提高散列表的性能。

考虑:

public class PhoneNumber {private final int areaCode;private final int prefix;private final int lineNumber;public PhoneNumber(int areaCode, int prefix, int lineNumber) {rangeCheck(areaCode, 999, "area code");rangeCheck(prefix, 999, "prefix");rangeCheck(lineNumber, 9999, "line number");this.areaCode = areaCode;this.prefix = prefix;this.lineNumber = lineNumber;}private static void rangeCheck(int arg, int max, String name) {if(arg < 0 || arg > max) {throw new IllegalArgumentException(name + ": " + arg);}}@Overridepublic boolean equals(Object o) {if(o == this)return true;if(!(o instanceof PhoneNumber))return false;PhoneNumber pn = (PhoneNumber)o;return pn.lineNumber == lineNumber&& pn.prefix == prefix&& pn.areaCode == areaCode;}}

运行下面代码:

Map<PhoneNumber, String> map = new HashMap<PhoneNumber, String>();
map.put(new PhoneNumber(707, 867, 5309), "Jenny");
System.out.println(map.get(new PhoneNumber(707, 867, 5309)));

我们期望它返回Jenny,然而它返回的是null。

原因在于违反了hashCode的约定,由于PhoneNumber没有覆盖hashCode方法,导致两个相等的实例拥有不相等的散列码,put方法把电话号码对象放在一个散列桶中,get方法从另外一个散列桶中查找这个电话号码的所有者,显然是无法找到的。

只要覆盖hashCode并遵守约定,就能修正这个问题。

一个好的散列函数倾向于“为不相等的对象产生不相等的散列码”,下面有简单的解决办法:

1.把某个非零的常数值,如17,保存在一个名为result的int类型的变量中。(为了2.a中计算的散列值为0的初始域会影响到散列值)

2.对于对象中的每个关键域f,完成一下步骤:

 a.为该域计算int类型的散列码c

  i.如果该域是boolean,计算(f ? 1:0)

  ii.如果该域是byte、char、short或者int类型,则计算(int)f

  iii.如果该域是long,则计算(int)(f ^ (f >>> 32))

  iv.如果该域是float,则计算Float.floatToIntBits(f)

  v.如果该域是double,则计算Double.doubleToLongBits(f),然后

  vi.如果该域是一个对象引用,并且该类的equals方法通过递归地调用equals的方式来比较这个域,则同样为这个域递归地调用hashCode。如果需要更复杂的比较,则为这个域计算一个“范式”,然后针对这个“范式”调用hashCode。如果域的值为null,则返回0(或其他某个常数,但通常为0)。

  vii.如果该域是一个数组,则要吧每一个元素当做单独的域来处理,也就是要递归地应用上述规则,对每个重要的元素计算一个散列码,然后根据2.b把这些散列值组合起来。如果数组域中的每个元素都很重要,可以使用1.5中增加的其中一个Array.hashCode方法。

 b.按照下面的公式,把步骤2.a中计算得到的散列码c合并到result中:

  result = 31 * result + c。(选择31是因为它是一个奇素数,如果乘数是偶数,乘法溢出时会丢失信息,VM可以优化 31 * i == (i << 5) - i)

3.返回result。

编写完hashCode方法后,编写单元测试来验证相同的实例是否有相等的散列码。

把上面的解决方法应用到PhoneNumber类中:

@Override
public int hashCode() {int result = 17;result = 31 * result + areaCode;result = 31 * result + prefix;result = 31 * result + lineNumber;return result;
}

现在使用之前的测试代码,发现能够返回Jenny了。

如果一个类是不可变的,并且计算散列码的开销很大,应该考虑把散列码缓存到对象内部而不是每次请求都重新计算散列码,如果这种类大多数对象会被用作散列键,应该在创建实例的时候计算散列码,否则可以选择延迟初始化散列码。

注意:不要试图从散列码计算中排除掉一个对象的关键部分来提高性能。虽然这样做运行起来可能更快,但效果不见得好,在拥有大量实例的时候,忽略的域区别仍然非常大,但散列函数仍然把它们映射到同样的散列桶中,例如Java 1.2之前实现的String散列函数至多检查16个字符,对于像URL这样的大型集合,散列函数表现出病态的行为(把第16个字符后相差非常大的URL映射到同样的散列桶中,使得碰撞率很高,性能降低)。

转载于:https://www.cnblogs.com/13jhzeng/p/5644368.html

第9条:覆盖equals时总要覆盖hashCode相关推荐

  1. java延迟覆盖_高效Java第九条覆盖equals时总要覆盖hashCode

    原标题:高效Java第九条覆盖equals时总要覆盖hashCode 高效Java第九条覆盖equals时总要覆盖hashCode 在每个覆盖了equals方法的类中,也必须覆盖hashCode方法. ...

  2. java代码优化equal_java代码优化——覆盖equals时总要覆盖hashCode

    HashCode规范 一个很常见的错误根源在于没有覆盖hashCode方法.在每个覆盖了equals方法的类中,也必须覆盖hashCode方法.如果不这样的话,就会违反Object.hashCode的 ...

  3. Effective Java 对于所用对象都通用的方法 8.覆盖equals时请遵守通用约定.txt

    对于eclipse覆盖equals方法就是Alt+Shift+S,而AS就是Alt+Insert.覆盖很简单,可是却容易导致错误,而且后果很严重.最容易避免的方法就是不覆盖,这样类就只与他自身相等. ...

  4. why在重写equals时还必须重写hashcode方法

    首先我们先来看下String类的源码:可以发现String是重写了Object类的equals方法的,并且也重写了hashcode方法 public boolean equals(Object anO ...

  5. 你所忽略的,覆盖equals时需要注意的事项《effective java》

    我们都知道Object的equals的比较其实就是==的比较,其实是内存中的存放地址的比较.正常逻辑上:类的每个实例本质上都是唯一的. 在工作中我们实际的业务逻辑往往有可能出现一些相对特殊的需求需要对 ...

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

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

  7. 为什么重写equals方法必须要重写hashCode方法

    为什么重写equals方法必须要重写hashCode方法 ​ 了解这个问题之前我们得需要知道hashCode的作用.equals方法和hashCode方法都是Object类中的基础方法,用来判断两个对 ...

  8. Effective Java之覆盖equal时要遵守通用约定(八)

    先介绍一下Object的equal作用,==代表物理上的相等,equal代表逻辑上的相等,Object的equal的方法其实等同于==,这是因为它的逻辑是"如果对象物理相等,那么它们就逻辑相 ...

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

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

最新文章

  1. 干货! AI 推断解决方案栈 Vitis AI 全流程独家解析
  2. [bzoj2527][Poi2011]Meteors_整体二分_树状数组
  3. 关于不能够精确的对浮点数进行运算的问题
  4. ABC181——F - Silver Woods
  5. 快速排序和归并排序的区别,Python代码实现
  6. NLP学习—22.Transformer的代码实现
  7. UVA-12661 Funny Car Racing (dijkstra)
  8. project euler 开坑
  9. android 音效均衡器,App+1 | 不懂均衡器调校也能量身定制,无需折腾的 Android 音效提升工具...
  10. 你有什么道理后悔没有早点知道?
  11. Mac安装Python并使用GUI界面设计
  12. 计算机关机的命令,电脑关机命令是什么
  13. 用泰勒级数展开证明欧拉公式
  14. glew库安装和初始化
  15. 计算机网络---网络层
  16. 百度地图如何在html中显示图片,在网页中插入百度地图(实例)
  17. 热释电传感器三个引脚_那些选对热释电传感器的人,都看过这篇!
  18. 前端 JS实现彩票开奖走势图 连线
  19. 关于小米mini路由器开启ssh红灯解决
  20. mpg123 听音乐

热门文章

  1. dalvik Java类库中本地类
  2. MMIX机器简要介绍
  3. gradle下载的依赖包位置 及 修改
  4. Java 比较两个日期的方法
  5. centos 忘记 root 密码
  6. 一个程序员的奋斗经历1
  7. 获取计算机的信息(IP地址、MAC地址、CUP序列号、硬盘序列号、主板信息等等)...
  8. (引)ajax 经验-保留自己使用
  9. linux 系统 权限
  10. es6 class extends