为什么覆盖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. 20172333 2017-2018-2 《Java程序设计》第10周学习总结

    20172333 2017-2018-2 <Java程序设计>第10周学习总结 教材学习内容 第十三章 集合是一个对象,一个保存其他对象的数据库. 集合可以保存不同种类的对象也可以保存同种 ...

  2. 20145214实验二 Java面向对象程序设计

    20145214实验二 Java面向对象程序设计 初步掌握单元测试和TDD 三种代码 伪代码 `百分制转五分制:` `如果成绩小于60,转成"不及格"` `如果成绩在60与70之间 ...

  3. oracle数据库中常见的操作语句(一)

    一 创建表空间 create tablespace lfdc_data logging datafile 'D:\Database\lfdc_data.dbf' size 50m autoextend ...

  4. 1001 Duplicate Pair

    1.题目戳这里 2.代码: #include<stdio.h> #include<string.h> int main() { int n; while(scanf(" ...

  5. PART1 一些想法

    其实我一直是一个后知后觉的人,这点也是我过了好久才发现的问题,之所以晚发现自己这个毛病,是因为后知后觉==,这有点像是个悖论或者是笑话,但的确是真实存在于我的身上.其实当初为啥来这个学校选计算机的专业 ...

  6. LintCode-66.二叉树的前序遍历

    二叉树的前序遍历 给出一棵二叉树,返回其节点值的前序遍历. 样例 给出一棵二叉树 {1,#,2,3}, 返回 [1,2,3]. 挑战 你能使用非递归实现么? 标签 递归 二叉树 二叉树遍历 非递归 c ...

  7. perf record -c

    如果perf record -c -c后面接的是sample_period,也就是说你让这个事件没 我的loop进程一直在执行,我的CPU的频率是2.6G hz,也就是说每一秒会有2,600,000, ...

  8. foreach循环2

    <select id="test" parameterType="java.util.List" resultType="user"& ...

  9. tomcat执行shutdown.sh进程残留的解决办法

    我们执行shutdown.sh指令的时候有时会发现进程并没有被关掉而是越来越多,这种情况一般是项目造成的,具体原因未去调查.由于tomcat自己有相应的保护机制,所以我们只需要强制结束其进程即可,下面 ...

  10. Linux中实现在系统启动时自动加载模块

    下面是以前学习Linux时写的,后来仔细研究rc.sysinit后发现,只需要修改下列地方就可以了,不必这么麻烦的: rc.sysinit中有这样的一段代码: # Load other user-de ...