第9条:覆盖equals时总要覆盖hashCode
在每个覆盖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); }
} @Override
public 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 = ;
result = * result + areaCode;
result = * result + prefix;
result = * result + lineNumber;
return result;
}
现在使用之前的测试代码,发现能够返回Jenny了。
如果一个类是不可变的,并且计算散列码的开销很大,应该考虑把散列码缓存到对象内部而不是每次请求都重新计算散列码,如果这种类大多数对象会被用作散列键,应该在创建实例的时候计算散列码,否则可以选择延迟初始化散列码。
注意:不要试图从散列码计算中排除掉一个对象的关键部分来提高性能。虽然这样做运行起来可能更快,但效果不见得好,在拥有大量实例的时候,忽略的域区别仍然非常大,但散列函数仍然把它们映射到同样的散列桶中,例如Java 1.2之前实现的String散列函数至多检查16个字符,对于像URL这样的大型集合,散列函数表现出病态的行为(把第16个字符后相差非常大的URL映射到同样的散列桶中,使得碰撞率很高,性能降低)。
第9条:覆盖equals时总要覆盖hashCode的更多相关文章
- Item 9 覆盖equals时总要覆盖hashCode
为什么覆盖equals时,总要覆盖hashCode? 原因是,根据Object规范: 如果两个对象根据equals(Object)方法比较是相等的,那么调用这两个对象中任意一个对象的hashCod ...
- 覆盖equals时总要覆盖hashCode
本文涉及到的概念 1.为什么重载equals方法时,要重载hashCode函数;没有重载hashCode带来的问题 2.一个对象hashCode的生成规则 1.为什么重载equals方法时 ...
- Effective Java —— 覆盖equals时总要覆盖hashCode
本文参考 本篇文章参考自<Effective Java>第三版第十一条"Always override hashCode when you override equals&quo ...
- 覆盖equals()时总要覆盖hashCode()
覆写如下: public class User{ private Integer id; private String userName; private String passWord; publi ...
- 覆盖equals 时总要覆盖hashCode(9)
2019独角兽企业重金招聘Python工程师标准>>> 1.在每个覆盖了equals 方法的类中,也必须覆盖hashCode 这是关于hashCode 的通用约定 这样可以与 基于散 ...
- 【Effective Java】5、覆盖equals时总要覆盖hashcode
package cn.xf.cp.ch02.item9; import java.util.HashMap; import java.util.Map; public class PhoneNumbe ...
- EffectiveJava(9)覆盖equals是总要覆盖hashCode
覆盖equals是总要覆盖hashCode 通过散列函数将集合中不相等的实例均匀的分布在所有可能的散列值上 1.把某个非零的常数值保存在一个名为result的int类型变量中 2.对于对象中每个关键域 ...
- 第8条:覆盖equals时请遵守通用约定
第8条:覆盖equals时请遵守通用约定 引言:尽管Object是一个具体类,但是设计它主要是为了拓展.它所有的非final方法(equals.hashCode.toString.clone和fina ...
- 第8条:覆盖equals时遵守通用约定
如果不需要覆盖equals方法,那么就无需担心覆盖equals方法导致的错误. 什么时候不需要覆盖equals方法? 1.类的每个实例本质上是唯一的. 例如对于Thread,Object提供的equa ...
随机推荐
- JavaScript 原型继承开端
1.原型继承本质 就javascript对象系统的实现来讲,对象并没有原型,而构造器有原型(构造器.prototype指向其原型).对象只有构造自某个原型的说法,并没有持有某个原型的说法. ...
- 使用openssl库实现des,3des加密
原文地址: 使用openssl库实现des,3des加密 主要是调整了一下格式,以及一些变量的类型,以解决在VC2008下无法编译通过的问题. #include <stdio.h> #in ...
- js防刷新的倒计时
近期在维护考试系统,在进行考试測试时无意中点击了刷新button.可是上面的倒计时并没有受到影响.同一时候在几篇博客中也有这种样例,所以我想看看它究竟是如何防止刷新的. 假设是用cs代码写.我们可能会 ...
- 46 关于Linux的I/O重定向
I/O重定向是一个过程,这个过程捕捉一个文件.或命令.或程序.或脚本.甚至代码块(code block)的输出,然后把捕捉到的输出,作为输入发送给另外一个文件.或命令.或程序.或脚本. 1.I/O重定 ...
- java中的url 编码与解码
什么是application/x-www-form-urlencoded字符串? 答:它是一种编码类型.当URL地址里包含非西欧字符的字符串时,系统会将这些字符转换成application/x-www ...
- 【分享】4412开发板-嵌入式Linux开发须要掌握的基础知识和技能
本文转自迅为电子论坛:http://www.topeetboard.com 1.Linux 基础 安装Linux操作系统 Linux文件系统 Linux经常使用命令 Linux启动过程具体解释 熟悉L ...
- PHP 开发者该知道的5个 Composer 小技巧
Composer是新一代的PHP依赖管理工具.其介绍和基本用法可以看这篇<Composer PHP依赖管理的新时代>.本文介绍使用Composer的五个小技巧,希望能给你的PHP开发带来方 ...
- vs2010问题 error MSB8008: 指定的平台工具集(v110)未安装或无效
vs2012上的项目导入到vs2010中,编译出现 >------ 已启动生成: 项目: HelloGame.win32, 配置: Debug Win32 ------ >生成启动时间为 ...
- JavaScript网站设计实践(四)编写about.html页面,利用JavaScript和DOM,选择性的显示和隐藏DIV元素
一.现在我们在网站设计(三)的基础上,来编写about.html页面. 这个页面要用到的知识点是利用JavaScript和DOM实现选择性地显示和隐藏某些DIV about.html页面在前面我们为了 ...
- word-wrap,word-break,text-wrap的区别
今晚看到了无双老师关于word-wrap,word-break区别的讲解 http://www.cnblogs.com/2050/archive/2012/08/10/2632256.html 受益 ...