Java基础:hashCode与equals个人学习记录
摘要:
本文主要记录本人对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方法(此篇博客我感觉写的特别好,这里感谢作者。)
以上内容还需慢慢补充,如文中有错误,请指点,多谢。