为什么覆盖equals时,总要覆盖hashCode?

 
原因是,根据Object规范:
如果两个对象根据equals(Object)方法比较是相等的,那么调用这两个对象中任意一个对象的hashCode方法都必须产生同样的整数结果。
如果违反这个规定,那么,在使用应用了散列码的集合(HashMap,HashSet,Hashtable)时,就会出现问题。
 
也就是,两个相等的对象,必须要有相等的散列码(hashCode)。两个对象,进行比较的时候,使用的关键域是一样的,然后,使用这些关键域作为参数来生成散列码。两个对象相应的关键域的值相等,那么,使用相等的关键域,使用同样的生成方法,那么,生成出来的散列码是相等的
 
 
两个对象相等,那么,它们的hashCode是相等的么?
答:是的。根据Object规范,这是必须满足的。
 
两个对象的hashCode相等,那么这两个对象是相等的么?
答:不一定。因为,equals的实现,有可能是使用其它方式,未必只是简单地比较hashCode方法中用到的关键域。所以,不能根据对象的hashCode来判定两个对象是否相等。
 
两个对象根据equals(Object)方法进行比较,它们不相等,那么,这两个对象的hasoCde一定是不同的么?
答:不一定。同样的道理,因为equals方法的实现,未必只是简单地比较hashCode方法中用到的关键域的值。
 
hashCode的生成方式:
 
使用---HashCodeBuilder,该类是参考<<effective java>>中描述的规则实现的。
 
例子:
import java.util.HashMap;
import java.util.Map; public final class PhoneNumber {
private final short areaCode ;
private final short prefix ;
private final short 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 = ( short) areaCode;
this. prefix = ( short) prefix;
this. lineNumber = (short ) 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;
} // Broken - no hashCode method! // A decent hashCode method - Page 48
// @Override public int hashCode() {
// int result = 17;
// result = 31 * result + areaCode;
// result = 31 * result + prefix;
// result = 31 * result + lineNumber;
// return result;
// } // Lazily initialized, cached hashCode - Page 49
// private volatile int hashCode; // (See Item 71)
//
// @Override public int hashCode() {
// int result = hashCode;
// if (result == 0) {
// result = 17;
// result = 31 * result + areaCode;
// result = 31 * result + prefix;
// result = 31 * result + lineNumber;
// hashCode = result;
// }
// return result;
// } public static void main(String[] args) {
Map<PhoneNumber, String> m = new HashMap<PhoneNumber, String>();
m.put (new PhoneNumber(707, 867, 5309), "Jenny");
System.out.println(m.get( new PhoneNumber(707, 867, 5309)));
}
}
---------------------------------------------
输出结果是:
null
 
原因:在覆盖equals方法的时候,没有覆盖hashCode方法。PhoneNumber作为HashMap的K,在main方法中,生成了两个PhoneNumber实例,这两个PhoneNumber实例通过equals方法比较是相等的。但是,PhoneNumber,并没有覆盖hashCode方法,所以,这两个实例具备不同的hashCode。现在要做的就是,覆盖hashCode方法,然后,使通过equals比较相等的两个PhoneNumber实例返回相等的hashCode。
 
HashCode生成方式,使用HashCodeBuilder:
下面的代码添加了一个覆盖的hashCode方法,输出结果,就找到了Jenny。
public final class PhoneNumber {
private final short areaCode ;
private final short prefix ;
private final short 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 = ( short) areaCode;
this. prefix = ( short) prefix;
this. lineNumber = (short ) 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;
} @Override
public int hashCode() {
return new HashCodeBuilder(17, 37).append(lineNumber).append(prefix ).append(areaCode)
.toHashCode();
} // Broken - no hashCode method! // A decent hashCode method - Page 48
// @Override public int hashCode() {
// int result = 17;
// result = 31 * result + areaCode;
// result = 31 * result + prefix;
// result = 31 * result + lineNumber;
// return result;
// } // Lazily initialized, cached hashCode - Page 49
// private volatile int hashCode; // (See Item 71)
//
// @Override public int hashCode() {
// int result = hashCode;
// if (result == 0) {
// result = 17;
// result = 31 * result + areaCode;
// result = 31 * result + prefix;
// result = 31 * result + lineNumber;
// hashCode = result;
// }
// return result;
// } public static void main(String[] args) {
Map<PhoneNumber, String> m = new HashMap<PhoneNumber, String>();
m.put(new PhoneNumber(707, 867, 5309), "Jenny");
System.out.println(m.get( new PhoneNumber(707, 867, 5309)));
}
}

确定关键域,关键域指的是覆盖的equals方法中涉及到的每个域;然后,使用这些关键域,作为参数,用它们生成一个hashCode。这个可以通过HashCodeBuilder来实现,具体参照上述代码。

  

 
 

Item 9 覆盖equals时总要覆盖hashCode的更多相关文章

  1. 覆盖equals时总要覆盖hashCode

    本文涉及到的概念 1.为什么重载equals方法时,要重载hashCode函数;没有重载hashCode带来的问题 2.一个对象hashCode的生成规则       1.为什么重载equals方法时 ...

  2. 第9条:覆盖equals时总要覆盖hashCode

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

  3. Effective Java —— 覆盖equals时总要覆盖hashCode

    本文参考 本篇文章参考自<Effective Java>第三版第十一条"Always override hashCode when you override equals&quo ...

  4. 覆盖equals 时总要覆盖hashCode(9)

    2019独角兽企业重金招聘Python工程师标准>>> 1.在每个覆盖了equals 方法的类中,也必须覆盖hashCode 这是关于hashCode 的通用约定 这样可以与 基于散 ...

  5. 覆盖equals()时总要覆盖hashCode()

    覆写如下: public class User{ private Integer id; private String userName; private String passWord; publi ...

  6. 【Effective Java】5、覆盖equals时总要覆盖hashcode

    package cn.xf.cp.ch02.item9; import java.util.HashMap; import java.util.Map; public class PhoneNumbe ...

  7. EffectiveJava(9)覆盖equals是总要覆盖hashCode

    覆盖equals是总要覆盖hashCode 通过散列函数将集合中不相等的实例均匀的分布在所有可能的散列值上 1.把某个非零的常数值保存在一个名为result的int类型变量中 2.对于对象中每个关键域 ...

  8. Item 8 覆盖equals时请遵守通用约定

    在覆盖equals方法的时候,你必须要遵守它的通用约定,不遵守,写出来的方法,会出现逻辑错误.下面是约定的内容:   equals方法实现了等价关系:   自反性.对于任何非null的引用值,x.eq ...

  9. 第8条:覆盖equals时遵守通用约定

    如果不需要覆盖equals方法,那么就无需担心覆盖equals方法导致的错误. 什么时候不需要覆盖equals方法? 1.类的每个实例本质上是唯一的. 例如对于Thread,Object提供的equa ...

随机推荐

  1. joomla 出现The file Cache Storage is not supported on this platform;

    错误提示:The file Cache Storage is not supported on this platform:在这个平台上不支持文件缓存存储 出现这样的原因很简单,有两个文件夹不可写,这 ...

  2. hive mapjoin优化

    默认为10MB,如果大于该值不会执行mapjoin,hive语句中直接设置的mapjoin也不再起作用. 参考hive wiki把hive.auto.convert.join.noconditiona ...

  3. pfx 证书怎么打开

    其实双击就能够自动运行导入向导的 不行的话使用我的办法: 单击开始--运行--里输入mmc 然后单击文件--选择添加删除管理单元--再选择添加--拉动滚动条找到证书一项,点击添加再点击完成(不用做任何 ...

  4. cURL和file_get_contents实现模拟post请求

    以前面试时候,面试官问过我后端有没有跨域问题,但是不敢肯定,现在可以肯定的说没有. 不文用php的cURL和file_get_contents方法分别实现后端跨域.本文场景也是在tp5下实现的. 一, ...

  5. kafka启动出现:Unsupported major.minor version 52.0 错误

    具体的错误输出: Exception in thread "main" java.lang.UnsupportedClassVersionError: kafka/Kafka : ...

  6. 使用 Python 操作 Git 版本库 - GitPython

    GitPython 是一个用于操作 Git 版本库的 python 包, 它提供了一系列的对象模型(库 - Repo.树 - Tree.提交 - Commit等) 用于操作版本库中的相应对象. 版本库 ...

  7. 显示系统中所有的socket信息

    netstat -aon /proc/net/tcp /proc/net/udp /proc/net/unix 相关的代码是:tcp4_seq_show(struct seq_file *file, ...

  8. centos7 nginx端口转发出现502的其中一种原因

    在排查了一系列可能的原因后仍无法解决,经资料查阅可能是SELinux造成,SELinux很强大但若配置不当也会造成很多组件无法正常使用,这里直接将其关闭: //打开配置文件 vi /etc/selin ...

  9. MAC搭建 PHP 环境

    安装homebrew homebrew是mac下非常好用的包管理器,会自动安装相关的依赖包,将你从繁琐的软件依赖安装中解放出来. 安装homebrew也非常简单,只要在终端中输入11: 1 ruby ...

  10. Android 打开照相机、获取相册图片、获取图片并裁减

    一.调用照相机 注:surfaceView在当Activity不在前台的时候,会被销毁(onPause方法之后,执行销毁方法)当Activity回到前台时,在Activity执行onResume方法之 ...