摘要:

本文主要记录本人对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. 好工具MyEclise2016 CI下载

    地址:http://pan.baidu.com/s/1gfBw9Ab 安装后,点开crack目录,按步骤走. 下面是我安装成功的画面.

  2. poj1691--Painting A Board(拓扑+dfs)

    题目链接:点击打开链接 题目大意:一个矩形由n个小矩形组成,如今要给小矩形染色,可是颜料会向下滑,为了防止弄乱颜料,所以要先染上面的矩形,后然染以下的矩形.每一次改变颜色都要用一个新的刷子.问最小用多 ...

  3. docker创建私有仓库及存储image

    Docker官方的Docker hub尽管提供了有非常多image,也基本上包括了我们须要使用的,可是其訪问起来比較慢.假设自己要定制image.多台server之间的共享使用此image非常不方便. ...

  4. hdfs笔记

    Distributed File System 数据量越来越多,在一个操作系统管辖的范围存不下了,那么就分配到更多的操作系统管理的磁盘中,但是不方便管理和维护,因此迫切需要一种系统来管理多台机器上的文 ...

  5. JSP简单练习-用Servlet获取表单数据

    // javaBean代码 package servlet; import java.io.*; import javax.servlet.*; import javax.servlet.http.* ...

  6. SQLSERVER 2008 链接 到 ORACLE 11

    MSSQL2008R2 链接 ORACLE 11: 创建链接: exec sp_addlinkedserver 'DBLINK_ORACL' , 'ORACLE' , 'MSDAORA' , 'ORC ...

  7. arcgis水文分析

    前言 1.在开始之前首先需要注意几点: 1.arcgis 需要 python2.7 的支持,并有必要的模块库,请一定注意避免与其他软件冲突,例如tecplot 2009 需要python2.5的支持, ...

  8. java中 ExecutorService,Executor,ThreadPoolExecutor的用法

    package com; import java.util.concurrent.BlockingQueue; import java.util.concurrent.Executor; import ...

  9. 不安装Oracle客户端也能使用PL/SQL

    解压缩 instantclient_12_1 到 D:\Oracle\instantclient_12_1 在文件夹内建立目录, /NETWORK/ADMIN 在该目录下,新建文件tnsnames.o ...

  10. Google论文BigTable拜读

    这周少打点dota2,争取把这篇论文读懂并呈现出来,和大家一起分享. 先把论文搞懂,然后再看下和论文搭界的知识,比如hbase,Chubby和Paxos算法. Bigtable: A Distribu ...