在我们刚开始学习Java的时候就被教导,在编写类的时候,如果覆盖了Object的equals方法,那么必须要覆盖hashCode方法,并且如果两个对象用equals方法比较返回true,那么这两个对象hashCode返回的值也必须是相等的,并且对于同一个对象,equals方法需要比较的属性值没有被修改,那么每次调用hashCode返回的值应该是一致的。
hashCode主要是用于散列集合,通过对象hashCode返回值来与散列中的对象进行匹配,通过hashCode来查找散列中对象的效率为O(1),如果多个对象具有相同的hashCode,那么散列数据结构在同一个hashCode位置处的元素为一个链表,需要通过遍历链表中的对象,并调用equals来查找元素。这也是为什么要求如果对象通过equals比较返回true,那么其hashCode也必定一致的原因。
为对象提供一个高效的hashCode算法是一个很困难的事情。理想的hashCode算法除了达到本文最开始提到的要求之外,还应该是为不同的对象产生不相同的hashCode值,这样在操作散列的时候就完全可以达到O(1)的查找效率,而不必去遍历链表。假设散列中的所有元素的hashCode值都相同,那么在散列中查找一个元素的效率就变成了O(N),这同链表没有了任何的区别。
这种理想的hashCode算法,如果是为具体业务的对象去设计应该不是很难,比如很多的数据库映射对象都存在一个整形的id属性,这个id属性往往在整个系统中是唯一的,那么hashCode在重写的时候返回这个id的值就可以了,equals比较的时候也是去比较id的值,并且对象在从数据库初始化之后是不可变的,这样就完全达到了理想的情况。这些对象保存在散列中,查找效率会是完全的O(1),不需要遍历任何链表。
本文着重讨论的是通用的hashCode实现,所谓的通用就是适合Java中每一个对象的hashCode算法实现。每个类的结构不尽相同,要想产生一个适合所有场景的理想hashCode算法几乎是不可能的,要设计通用的hashCode算法,我们只能去不断接近理想的情况。下面是几种实现方式。
《Effective Java》中推荐的实现方式
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; }Arrays.hashCode(...)只会计算一维数组元素的hashCOde,如果是多维数组,那么需要递归进行hashCode的计算,那么就需要使用Arrays.deepHashCode(Object[])方法。
import java.io.Serializable; public class Test implements Serializable { private static final long serialVersionUID = 1L; private final int[] array; public Test(int... a) { array = a; } @Override public int hashCode() { int result = 0; //注意,此处初始值为0 for (int element : array) { result = 31 * result + element; } return result; } public static void main(String[] args) { Test t = new Test(0, 0, 0, 0); Test t2 = new Test(0, 0, 0); System.out.println(t.hashCode()); System.out.println(t2.hashCode()); } }如果hashCode中result的初始值为0,那么对象t和对象t2的hashCode值都会为0,尽管这两个对象不同。但如果result的值为17,那么计算hashCode的时候就不会忽略这些为0的值,最后的结果t1是15699857,t2是506447
private volatile int hashCode = 0; @Override public int hashCode() { int result = hashCode; if(result == 0) { ...//计算过程 } return result; }注意,缓存属性必须是volatile的,这样可以在并发访问环境中保持内存可见性。否则会产生线程安全问题。
/** * Returns a hash code for this {<a href="http://my.oschina.net/codeo" target="_blank" rel="nofollow">@code</a> Long}. The result is * the exclusive OR of the two halves of the primitive * {<a href="http://my.oschina.net/codeo" target="_blank" rel="nofollow">@code</a> long} value held by this {<a href="http://my.oschina.net/codeo" target="_blank" rel="nofollow">@code</a> Long} * object. That is, the hashcode is the value of the expression: * * <blockquote> * {<a href="http://my.oschina.net/codeo" target="_blank" rel="nofollow">@code</a> (int)(this.longValue()^(this.longValue()>>>32))} * </blockquote> * * @return a hash code value for this object. */ public int hashCode() { return (int)(value ^ (value >>> 32)); }java.lang.Float的hashCode实现:
/** * Returns a hash code for this {<a href="http://my.oschina.net/codeo" target="_blank" rel="nofollow">@code</a> Float} object. The * result is the integer bit representation, exactly as produced * by the method {<a href="http://my.oschina.net/link1212" target="_blank" rel="nofollow">@link</a> #floatToIntBits(float)}, of the primitive * {<a href="http://my.oschina.net/codeo" target="_blank" rel="nofollow">@code</a> float} value represented by this {<a href="http://my.oschina.net/codeo" target="_blank" rel="nofollow">@code</a> Float} * object. * * @return a hash code value for this object. */ public int hashCode() { return floatToIntBits(value); }java.lang.double的hashCode实现:
/** * Returns a hash code for this {<a href="http://my.oschina.net/codeo" target="_blank" rel="nofollow">@code</a> Double} object. The * result is the exclusive OR of the two halves of the * {<a href="http://my.oschina.net/codeo" target="_blank" rel="nofollow">@code</a> long} integer bit representation, exactly as * produced by the method {<a href="http://my.oschina.net/link1212" target="_blank" rel="nofollow">@link</a> #doubleToLongBits(double)}, of * the primitive {<a href="http://my.oschina.net/codeo" target="_blank" rel="nofollow">@code</a> double} value represented by this * {<a href="http://my.oschina.net/codeo" target="_blank" rel="nofollow">@code</a> Double} object. That is, the hash code is the value * of the expression: * * <blockquote> * {<a href="http://my.oschina.net/codeo" target="_blank" rel="nofollow">@code</a> (int)(v^(v>>>32))} * </blockquote> * * where {<a href="http://my.oschina.net/codeo" target="_blank" rel="nofollow">@code</a> v} is defined by: * * <blockquote> * {<a href="http://my.oschina.net/codeo" target="_blank" rel="nofollow">@code</a> long v = Double.doubleToLongBits(this.doubleValue());} * </blockquote> * * @return a {<a href="http://my.oschina.net/codeo" target="_blank" rel="nofollow">@code</a> hash code} value for this object. */ public int hashCode() { long bits = doubleToLongBits(value); return (int)(bits ^ (bits >>> 32)); }org.apache.commons.lang.builder.HashCodeBuilder的实现
boolean testTransients, Class reflectUpToClass, String[] excludeFields)
其余的版本只是提供不同的默认参数,从而简化了构建的过程。比如:
public static int reflectionHashCode(Object object) { return reflectionHashCode(17, 37, object, false, null, null); }
然后构建的过程是这样的,除了指定过滤的,比如transient属性、excludeFields指定的属性之外,会遍历其它的类属性,然后通过反射的方式获取属性值,如果属性是数组,则会遍历数组,否则会调用属性的hashCode, 如果是多维数组,会去递归取hashCode值,对单个属性计算hash值的代码如下:
public HashCodeBuilder append(Object object) { if (object == null) { iTotal = iTotal * iConstant; } else { if(object.getClass().isArray()) { // 'Switch' on type of array, to dispatch to the correct handler // This handles multi dimensional arrays if (object instanceof long[]) { append((long[]) object); } else if (object instanceof int[]) { append((int[]) object); } else if (object instanceof short[]) { append((short[]) object); } else if (object instanceof char[]) { append((char[]) object); } else if (object instanceof byte[]) { append((byte[]) object); } else if (object instanceof double[]) { append((double[]) object); } else if (object instanceof float[]) { append((float[]) object); } else if (object instanceof boolean[]) { append((boolean[]) object); } else { // Not an array of primitives append((Object[]) object); } } else { iTotal = iTotal * iConstant + object.hashCode(); } } return this; }
这里要小心,因为它是直接调用属性对象的hashCode,如果是基本类型,那么就会调用包装器中提供的hashCode方法,如果是引用类型,那么需要仔细检查引用类型的hashCode方法,以免产生违反hashCode基本原则的情况。
然后剩下的计算过程,同Effective Java中描述的基本类似,不过这里的hash初值和乘数因子可以自己来设置,默认的情况是使用了17 和 37两个互质数。
HashCodeBuilder最大好处是使用方便,一行代码就能搞定hashCode的重写问题,并且让代码很清晰,但是它有这么几个值得注意的地方:
1. 使用反射会对程序的性能造成影响,不过Java反射机制为了把性能影响降到最低,对类似getFields()之类的操作都采用了Cache策略,对一般的程序而言,这些性能开销往往可以忽略不计。另外如果使用的是不可变对象,那么强烈建议把hashCode Cache住,这样可以极大的提高hashCode计算的性能。
2. 因为默认会处理所有的field(除了transient修饰的field),所以一定要测试是否违反hashCode的基本原则(为了保障基本原则的正确,建议跟org.apache.commons.lang.EqualsBuilder搭配使用),尤其是当类的域中包含引用类型的时候,一定要递归检查引用类型的hashCode.
链式的HashCodeBuilder
import java.util.Arrays; /** * 一个链式调用的通用hashCode生成器 * * <a href="http://my.oschina.net/arthor" target="_blank" rel="nofollow">@author</a> hongze.chi@gmail.com * */ public final class HashCodeHelper { private static final int multiplierNum = 31; private int hashCode; private HashCodeHelper() { this(17); } private HashCodeHelper(int hashCode) { this.hashCode = hashCode; } public static HashCodeHelper getInstance() { return new HashCodeHelper(); } public static HashCodeHelper getInstance(int hashCode) { return new HashCodeHelper(hashCode); } public HashCodeHelper appendByte(byte val) { return appendInt(val); } public HashCodeHelper appendShort(short val) { return appendInt(val); } public HashCodeHelper appendChar(char val) { return appendInt(val); } public HashCodeHelper appendLong(long val) { return appendInt((int) (val ^ (val >>> 32))); } public HashCodeHelper appendFloat(float val) { return appendInt(Float.floatToIntBits(val)); } public HashCodeHelper appendDouble(double val) { return appendLong(Double.doubleToLongBits(val)); } public HashCodeHelper appendBoolean(boolean val) { return appendInt(val ? 1 : 0); } public HashCodeHelper appendObj(Object... val) { return appendInt(Arrays.deepHashCode(val)); } public HashCodeHelper appendInt(int val) { hashCode = hashCode * multiplierNum + val; return this; } public int getHashCode() { return this.hashCode; } }通过这种链式调用的方式,没有反射的开销,另外可以比较方便的选择要参与计算的属性,代码也比较清晰,但是如果参与计算的属性值过多,那么会造成调用链过长的情况。为保持代码的整洁,也可以分多个链来调用。示例代码:
import java.io.Serializable; public class Test implements Serializable{ private static final long serialVersionUID = 1L; private final int[] array; public Test(int... a) { array = a; } @Override public int hashCode() { HashCodeHelper hashCodeHelper = HashCodeHelper.getInstance(); hashCodeHelper.appendInt(array[0]).appendInt(array[1]).appendInt(array[2]); hashCodeHelper.appendInt(array[3]).appendInt(array[4]).appendInt(array[5]); return hashCodeHelper.getHashCode(); } }转载自:http://my.oschina.net/chihz/blog/56256
相关推荐
- **Object:宇宙超级类**:探讨了Object类作为所有Java类的根类的重要性,以及如何重写其提供的方法(如equals、hashCode等)。 - **泛型数组列表**:介绍了泛型的概念,并通过具体的例子说明如何使用泛型数组列表...
### Java核心技术(原书第12版) #### 核心概念与语法 - **面向对象编程**:Java是一种完全的...这些建议和技术细节不仅有助于提高代码质量和效率,还能加深对Java平台的理解,进而写出更加健壮和高性能的应用程序。
### SCJP Sun® Certified Programmer for Java™ 6 Study...此外,了解如何正确地重写`equals()`和`hashCode()`方法,以及如何利用Java集合框架提供的功能进行高效的数据管理和操作,对于提高软件质量和性能至关重要。
在阅读和研究《Effective Java》中的源码时,我们可以关注以下几个关键知识点: 1. **单例模式**:书中的Item 3 "考虑单例类" 提到了多种实现单例的策略,如懒汉式、饿汉式以及枚举单例。理解这些方式的优缺点对于...
本章主要探讨了以下几个核心知识点: 1. **类的继承**:继承是一种面向对象编程的基本特性,允许一个类(派生类)从另一个类(基类或父类)继承属性和方法。通过继承,子类可以获得父类的功能,并可以添加自己的...
在Java中,`myid_examples` 可能涵盖以下几个关键领域: 1. **基本数据类型和变量**:在Java中,`int` 和 `long` 数据类型常用于表示整型ID,而 `String` 类型则用于存储文本形式的ID。了解这些基本类型以及如何...