摘要:

本文主要记录本人对hashCode和对equals两个知识点的学习过程。

从学生时期初学java,就知道hashCode和equals这两个方法,工作中equals方法使用也是特别频繁,要说equals方法,那么必须少不了hashCode这个方法。下面就整理一下本人目前对这俩方法的理解,文中如有错误,请指正,多谢。

hash code(散列码,也可以叫哈希码值)是对象产生的一个整型值。其生成没有规律的。二者散列码可以获取对象中的信息,转成那个对象的“相对唯一”的整型值。所有对象都有一个散列码。

直接贴出JDk源码,清晰明了。

Object:

public native int hashCode();

public boolean equals(Object obj) {
return (this == obj);
} 

我们注意到,Object的hashCode前面有个native修饰符,这个修饰符是什么意思呢?

native是本地的意思,native修饰的方法称为本地方法。在java源程序中以关键字“native”声明,不提供函数体,其实现使用C/C++语言在另外的文件中编写,编写的规则遵循Java本地接口的规范(简称JNI)。简而言就是Java中声明的可调用的使用C/C++实现的方法。再简单的说就是Object对象的hashCode方法的实现由非java语言在外部实现,比如C(想要进一步了解native,请自行搜索),这里的hashCode方法返回的是内存对象的地址。

Object对象的equals方法比较的是对象的内存地址是否相等。

接着看一下String这个类的这两个方法:

/** The value is used for character storage. */
private final char value[]; /** Cache the hash code for the string */
private int hash; // Default to 0 public int hashCode() {
int h = hash;
if (h == 0 && value.length > 0) {
char val[] = value; for (int i = 0; i < value.length; i++) {
h = 31 * h + val[i];
}
hash = h;
}
return h;
}

h = 31 * h + val[i];这里的计算为什么要用31呢?
看看《Effective Java》第二版第三章第42页中的原话:“之所以选择31,是因为它是个奇素数,如果乘数是偶数,并且乘法溢出的话,信息就会丢失,因为与2相乘等价于移位运算。使用素数的好处并不是很明显,但是习惯上都使用素数来计算散列结果。31有个很好的特性,就是用移位和减法来代替乘法,可以得到更好的性能:31*i==(i<<5)-i。现在的VM可以自动完成这种优化。”

String:

看看String的equals方法:

public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String anotherString = (String) anObject;
int n = value.length;
if (n == anotherString.value.length) {
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
while (n-- != 0) {
if (v1[i] != v2[i])
return false;
i++;
}
return true;
}
}
return false;
}

1、if (this == anObject) 首先判断是否是同一个对象,
2、如果传入参数anObject是String类型,那么就循环迭代这两个字符串对应的字符数组中的每一个值是否相等,如有一个字符不相等,则返回false。

Byte

接着看一下Byte:

public int hashCode() {
return (int)value;
} public boolean equals(Object obj) {
if (obj instanceof Byte) {
return value == ((Byte)obj).byteValue();
}
return false;
}

Character:

public int hashCode() {
return (int)value;
} public boolean equals(Object obj) {
if (obj instanceof Character) {
return value == ((Character)obj).charValue();
}
return false;
}

Short:

 public int hashCode() {
return (int)value;
} public boolean equals(Object obj) {
if (obj instanceof Short) {
return value == ((Short)obj).shortValue();
}
return false;
}

Integer:

 public int hashCode() {
return value;
} public boolean equals(Object obj) {
if (obj instanceof Integer) {
return value == ((Integer)obj).intValue();
}
return false;
}

上面四种类型这两个方法代码比较简单,一目了然。

Float

看看Float:

public int hashCode() {
return floatToIntBits(value);
} public boolean equals(Object obj) {
return (obj instanceof Float)
&& (floatToIntBits(((Float)obj).value) == floatToIntBits(value));
}
floatToIntBits()?这个方法是什么鬼?贴出源码:
public static int floatToIntBits(float value) {
int result = floatToRawIntBits(value);
// Check for NaN based on values of bit fields, maximum
// exponent and nonzero significand.
if ( ((result & FloatConsts.EXP_BIT_MASK) ==
FloatConsts.EXP_BIT_MASK) &&
(result & FloatConsts.SIGNIF_BIT_MASK) != 0)
result = 0x7fc00000;
return result;
}

不知道什么意思,网上搜到一个解释,也贴在这里:

“按照IEEE 754标准,32位浮点数在计算机中二进制存储形式共三部分:S(1位,符号) E(8位,阶码) M(23位,尾数)
举个例子,Float.floatToIntBits(20.5f)按照如下方式计算:
20.59D=10100.1B=1.01001*2^4B 指数e=4
S=0-->正数  E=4+127=131D=10000011B-->真实指数e变成阶码E时需加127  M=01001B
则32位2进制存储形式为:0 10000011 01001000000000000000000
转换成10进制即1101266944"

看到这里,意思就是将浮点数转为int值,相互比较,这里比较的也是值。

Double

接着看看Double类型:

public int hashCode() {
long bits = doubleToLongBits(value);
return (int)(bits ^ (bits >>> 32));
} public boolean equals(Object obj) {
return (obj instanceof Double)
&& (doubleToLongBits(((Double)obj).value) ==
doubleToLongBits(value));
}

doubleToLongBits(),依旧是不明白,贴出源码。

public static long doubleToLongBits(double value) {
long result = doubleToRawLongBits(value);
// Check for NaN based on values of bit fields, maximum
// exponent and nonzero significand.
if ( ((result & DoubleConsts.EXP_BIT_MASK) ==
DoubleConsts.EXP_BIT_MASK) &&
(result & DoubleConsts.SIGNIF_BIT_MASK) != 0L)
result = 0x7ff8000000000000L;
return result;
}

网上关于doubleToLongBits()的解释,贴出来,帮助理解:

doubleToLongBits方法:根据 IEEE 754 浮点双精度格式 ("double format") 位布局,返回指定浮点值的表示形式。第 63 位(掩码 0x8000000000000000L 选定的位)表示浮点数的符号,第62~52位(掩码 0x7ff0000000000000L 选定的位)表示指数,第51~0位(掩码 0x000fffffffffffffL 选定的位)表示浮点数的有效数字(有时也称为尾数)。如果参数是正无穷大,则结果为 0x7ff0000000000000L;如果参数是负无穷大,则结果为 0xfff0000000000000L;如果参数是 NaN,则结果为 0x7ff8000000000000L。在所有情况下,结果都是一个 long 整数,将其赋予 longBitsToDouble(long) 方法将生成一个与 doubleToLongBits 的参数相同的浮点值(所有 NaN 值被压缩成一个“规范”NaN 值时除外)。

可知,Double类型比较的也是值。

Boolean

看看Boolean类型的俩方法:

 public int hashCode() {
return value ? 1231 : 1237;
} public boolean equals(Object obj) {
if (obj instanceof Boolean) {
return value == ((Boolean)obj).booleanValue();
}
return false;
}

Boolean类型的hashCode方法是什么意思?求大神解释......

Arrays

看看Arrays的这两个方法:

hashCode()源码:

// long类型数组
public static int hashCode(long a[]) {
if (a == null)
return 0; int result = 1;
for (long element : a) {
int elementHash = (int)(element ^ (element >>> 32));
result = 31 * result + elementHash;
} return result;
} // int数组
public static int hashCode(int a[]) {
if (a == null)
return 0; int result = 1;
for (int element : a)
result = 31 * result + element; return result;
} // short数组
public static int hashCode(short a[]) {
if (a == null)
return 0; int result = 1;
for (short element : a)
result = 31 * result + element; return result;
} // char数组
public static int hashCode(char a[]) {
if (a == null)
return 0; int result = 1;
for (char element : a)
result = 31 * result + element; return result;
} // byte 数组
public static int hashCode(byte a[]) {
if (a == null)
return 0; int result = 1;
for (byte element : a)
result = 31 * result + element; return result;
} // boolean 数组
public static int hashCode(boolean a[]) {
if (a == null)
return 0; int result = 1;
for (boolean element : a)
result = 31 * result + (element ? 1231 : 1237); return result;
} // float 数组
public static int hashCode(float a[]) {
if (a == null)
return 0; int result = 1;
for (float element : a)
result = 31 * result + Float.floatToIntBits(element); return result;
} // double 数组
public static int hashCode(double a[]) {
if (a == null)
return 0; int result = 1;
for (double element : a) {
long bits = Double.doubleToLongBits(element);
result = 31 * result + (int)(bits ^ (bits >>> 32));
}
return result;
} // Object 数组
public static int hashCode(Object a[]) {
if (a == null)
return 0; int result = 1; for (Object element : a)
result = 31 * result + (element == null ? 0 : element.hashCode()); return result;
}

  数组是迭代计算数组中每个元素的散列值,获取hash值。

equals()方法源码:

 public static boolean equals(long[] a, long[] a2) {
if (a==a2)
return true;
if (a==null || a2==null)
return false; int length = a.length;
if (a2.length != length)
return false; for (int i=0; i<length; i++)
if (a[i] != a2[i])
return false; return true;
} public static boolean equals(int[] a, int[] a2) {
if (a==a2)
return true;
if (a==null || a2==null)
return false; int length = a.length;
if (a2.length != length)
return false; for (int i=0; i<length; i++)
if (a[i] != a2[i])
return false; return true;
} public static boolean equals(short[] a, short a2[]) {
if (a==a2)
return true;
if (a==null || a2==null)
return false; int length = a.length;
if (a2.length != length)
return false; for (int i=0; i<length; i++)
if (a[i] != a2[i])
return false; return true;
} public static boolean equals(char[] a, char[] a2) {
if (a==a2)
return true;
if (a==null || a2==null)
return false; int length = a.length;
if (a2.length != length)
return false; for (int i=0; i<length; i++)
if (a[i] != a2[i])
return false; return true;
} public static boolean equals(byte[] a, byte[] a2) {
if (a==a2)
return true;
if (a==null || a2==null)
return false; int length = a.length;
if (a2.length != length)
return false; for (int i=0; i<length; i++)
if (a[i] != a2[i])
return false; return true;
} public static boolean equals(boolean[] a, boolean[] a2) {
if (a==a2)
return true;
if (a==null || a2==null)
return false; int length = a.length;
if (a2.length != length)
return false; for (int i=0; i<length; i++)
if (a[i] != a2[i])
return false; return true;
} public static boolean equals(double[] a, double[] a2) {
if (a==a2)
return true;
if (a==null || a2==null)
return false; int length = a.length;
if (a2.length != length)
return false; for (int i=0; i<length; i++)
if (Double.doubleToLongBits(a[i])!=Double.doubleToLongBits(a2[i]))
return false; return true;
} public static boolean equals(float[] a, float[] a2) {
if (a==a2)
return true;
if (a==null || a2==null)
return false; int length = a.length;
if (a2.length != length)
return false; for (int i=0; i<length; i++)
if (Float.floatToIntBits(a[i])!=Float.floatToIntBits(a2[i]))
return false; return true;
} public static boolean equals(Object[] a, Object[] a2) {
if (a==a2)
return true;
if (a==null || a2==null)
return false; int length = a.length;
if (a2.length != length)
return false; for (int i=0; i<length; i++) {
Object o1 = a[i];
Object o2 = a2[i];
if (!(o1==null ? o2==null : o1.equals(o2)))
return false;
} return true;
}

数组的equals()方法是原有数组对象与参数数组对象中每个元素是否相等,当然前面还有一些判断。

搞到现在,主要目标是想搞明白java里是怎么比较两个对象是否相等。关于里面具体是实现思想,还需要慢慢推敲。

小结:

根据《effective java》这本书的内容,进行总结一下:

我们若要重写equals方法时,必须遵守他的通用约定,否则会出现异常。

  • 对称性(symmetric):任何非空=null引用值x y,如果x.equals(y)返回是“true”,那么y.equals(x)也应该返回是“true”。
  • 自反性(reflexive):任何非空=null引用值 x,x.equals(x)必须返回是“true”。
  • 传递性(transitive):任何非空=null引用值x y z ,如果x.equals(y)返回是“true”,而且y.equals(z)返回是“true”,那么z.equals(x)也应该返回是“true”。
  • 一致性(consistent):任何非空=null引用值x y,如果x.equals(y)返回是“true”,只要x和y内容一直不变,不管你重复x.equals(y)多少次,返回都是“true”。
  • 任何情况下,x.equals(null),永远返回是“false”;x.equals(和x不同类型的对象)永远返回false

当我们重写equals方法时,也要重写hashCode方法,因为我们要保证,如果两个对象equals等于true,那么hashCode返回值必须也是相等的

1、当两个对象equals方法返回true,那么hashCode返回值一定是相等的;如果两个对象equals方法返回值是false,那么这两个对象的hashCode的返回值有可能相等。

2、当两个对象的hashCode返回值相等时,两个对象的equals方法不一定true(hash冲突);但是如果两个对象的hashCode返回值不相等,那么这个对象equals返回值一定是false.

参考资料:

1、《Effective Java》 第二版

2、浅析hashCode方法(此篇博客我感觉写的特别好,这里感谢作者。)

以上内容还需慢慢补充,如文中有错误,请指点,多谢。

  

Java基础:hashCode与equals个人学习记录的更多相关文章

  1. 《java从入门到精通》学习记录

    目录 <Java从入门到精通>学习记录 3 基础的基础部分: 3 一. 常量与变量 3 1. 掌握: 3 (1) .常量与变量的声明方式: 3 (2) .变量的命名规则: 3 (3) .变 ...

  2. java基础(十六)----- equals()与hashCode()方法详解 —— 面试必问

    本文将详解 equals()与hashCode()方法 概述 java.lang.Object类中有两个非常重要的方法: public boolean equals(Object obj) publi ...

  3. 深入探究Java中hashCode()和equals()的关系

    目录 一.基础:hashCode() 和 equals() 简介 equals() hashCode() 二. 漫谈:初识 hashCode() 与 equals() 之间的关系 三. 解密:深入理解 ...

  4. Java中hashcode,equals和==

    hashcode方法返回该对象的哈希码值. hashCode()方法可以用来来提高Map里面的搜索效率的,Map会根据不同的hashCode()来放在不同的位置,Map在搜索一个对象的时候先通过has ...

  5. java中hashcode()和equals()的详解

    今天下午研究了半天hashcode()和equals()方法,终于有了一点点的明白,写下来与大家分享(zhaoxudong 2008.10.23晚21.36). 1. 首先equals()和hashc ...

  6. 【Java】hashcode()和equals()

    大家知道,在集合中判断集合中的两个元素是否相同,依赖的是hashcode()和equals()两个方法. > 一个简单的实验 public class Teacher { private Int ...

  7. java中hashcode和equals的区别和联系

    HashSet和HashMap一直都是JDK中最常用的两个类,HashSet要求不能存储相同的对象,HashMap要求不能存储相同的键. 那么Java运行时环境是如何判断HashSet中相同对象.Ha ...

  8. java 中hashcode和equals 总结

    一.概述            在Java中hashCode的实现总是伴随着equals,他们是紧密配合的,你要是自己设计了其中一个,就要设计另外一个.当然在多数情况下,这两个方法是不用我们考虑的,直 ...

  9. java的HashCode和equals

    什么时候用到hashcode,什么时候用到equals? 首先java为每个对象都生成有默认的hashcode,这个java core里说是java对象的内存地址,但是equals方法里比较的也是对象 ...

随机推荐

  1. windows8开发-关于wp7应用迁移到win8 metro风格

    虽然微软说,wp7应用移植到win8上面是比较简单,只需要修改部分API和设计原则上的细节,同时它也提供了一份比较简洁的参考文档: 而实际上这种移植的工作量还是不小的,尤其当应用引用了较多底层的API ...

  2. LeetCode OJ 之 Ugly Number (丑数)

    题目: Write a program to check whether a given number is an ugly number. Ugly numbers are positive num ...

  3. Node.js学习入门手册

    Node.js 安装 1.下载http://nodejs.org/dist/v0.12.1/node-v0.12.1-x86.msi并完成安装 2.下载https://www.python.org/f ...

  4. HTML5 Canvas 绘制五角星

    代码: <!DOCTYPE html> <html lang="utf-8"> <meta http-equiv="Content-Type ...

  5. kettle转换之多线程

    kettle转换之多线程   ETL项目中性能方面的考虑一般是最重要的.特别是所讨论的任务频繁运行,或一些列的任务必须在固定的时间内运行.本文重点介绍利用kettle转换的多线程特性.以优化其性能. ...

  6. C#按指定长度分割字符串

    C#按指定长度分割字符串   这几天学习分析声音的波形数据,接收到的是十六进制的数据,需要将数据转换成十进制再绘图,这个过程涉及到字符串的分割,正好可以促进自己对C#相关知识的学习.说到分割字符串,我 ...

  7. Nginx配置SSL安全证书避免启动输入Enter PEM pass phrase

    之前两篇文章已经很好的介绍了Nginx配置SSL的一些情况,配置好的Nginx每次启动都要 输两遍PEM pass phrase,很是不爽,尤其是在服务器重启后,Nginx压根就无法自动启动,必须手动 ...

  8. 27:简单错误记录SimpleErrorLog

    题目描述 开发一个简单错误记录功能小模块,能够记录出错的代码所在的文件名称和行号. 处理: 1. 记录最多8条错误记录,循环记录,对相同的错误记录(净文件名称和行号完全匹配)只记录一条,错误计数增加: ...

  9. glob (programming) and spool (/var/spool)

    http://en.wikipedia.org/wiki/Glob_(programming) In computer programming, in particular in a Unix-lik ...

  10. refresh的停车场(栈和队列的STL)

    refresh的停车场 Time Limit: 1000ms   Memory limit: 65536K  有疑问?点这里^_^ 题目描写叙述  refresh近期发了一笔横財,开了一家停车场. 因 ...