《Effective Java》中推荐的hashCode算法

  • Post author:
  • Post category:java


http://blog.csdn.net/error_case/article/details/46503103

Google首席Java架构师Joshua Bloch在他的著作《Effective Java》中提出了一种简单通用的hashCode算法:

1. 初始化一个整形变量,为此变量赋予一个非零的常数值,比如


int result =



17



;
2. 选取equals方法中用于比较的所有域,然后针对每个域的属性进行计算:

(1) 如果是boolean值,则计算


f ? 1:0

(2) 如果是byte\char\short\int,则计算


(int)f

(3) 如果是long值,则计算(int)(


f ^ (f >>> 32)


)

(4) 如果是float值,则计算


Float.floatToIntBits(f)

(5) 如果是double值,则计算


Double.doubleToLongBits(f)


,然后返回的结果是long,再用规则(3)去处理long,得到int

(6) 如果是对象应用,如果equals方法中采取递归调用的比较方式,那么hashCode中同样采取


递归调用hashCode的方式


。否则需要为这个域计算一个范式,比如当这个域的值为null的时候,那么hashCode 值为0

(7) 如果是数组,没必要自己去重新遍历一遍数组,


java.util.Arrays.hashCode


方法包含了8种基本类型数组和引用数组的hashCode计算,算法同上,
java.util.Arrays.hashCode(long[])的具体实现:


  1. public




    static




    int


    hashCode(


    long


    a[]) {



  2. if


    (a ==


    null


    )



  3. return




    0


    ;




  4. int


    result =


    1


    ;



  5. for


    (


    long


    element : a) {



  6. int


    elementHash = (


    int


    )(element ^ (element >>>


    32


    ));


  7. result =

    31


    * result + elementHash;


  8. }



  9. return


    result;


  10. }



Arrays.hashCode(…)只会计算一维数组元素的hashCOde,如果是多维数组,那么需要递归进行hashCode的计算,那么就需要使用Arrays.deepHashCode(Object[])方法。


3. 最后,要如同上面的代码,把每个域的散列码合并到result当中:


result = 31 * result + elementHash


;

4. 测试,hashCode方法是否符合文章开头说的基本原则,这些基本原则虽然不能保证性能,但是可以保证不出错。
这个算法存在这么几个问题需要探讨:
1. 为什么初始值要使用非0的整数?这个的目的主要是为了减少hash冲突,考虑这么个场景,如果初始值为0,并且计算hash值的前几个域hash值计算都为0,那么这几个域就会被忽略掉,但是初始值不为0,这些域就不会被忽略掉,示例代码:


  1. import


    java.io.Serializable;



  2. public




    class


    Test


    implements


    Serializable {




  3. private




    static




    final




    long


    serialVersionUID = 1L;




  4. private




    final




    int


    [] array;




  5. public


    Test(


    int


    … a) {


  6. array = a;

  7. }



  8. @Override





  9. public




    int


    hashCode() {



  10. int


    result =


    0


    ;


    //注意,此处初始值为0





  11. for (int element : array) {




  12. result =

    31


    * result + element;


  13. }


  14. return


    result;


  15. }



  16. public




    static




    void


    main(String[] args) {


  17. Test t =

    new


    Test(


    0


    ,


    0


    ,


    0


    ,


    0


    );


  18. Test t2 =

    new


    Test(


    0


    ,


    0


    ,


    0


    );


  19. System.out.println(t.hashCode());

  20. System.out.println(t2.hashCode());

  21. }


  22. }


如果hashCode中result的初始值为0,那么对象t和对象t2的hashCode值都会为0,尽管这两个对象不同。但如果result的值为17,那么计算hashCode的时候就不会忽略这些为0的值,最后的结果t1是15699857,t2是506447


2.


为什么每次需要使用乘法去操作result? 主要是为了使散列值依赖于域的顺序


,还是上面的那个例子,Test t = new Test(1, 0)跟Test t2 = new Test(0, 1), t和t2的最终hashCode返回值是不一样的。

3. 为什么是31?


31是个神奇的数字,因为任何数n * 31就可以被JVM优化为 (n << 5) -n


,移位和减法的操作效率要比乘法的操作效率高的多。

例子:

 
import java.util.Arrays;
import java.util.List;
import java.util.Map;


public class HashTest {

	 int intVar;
	 long longVar;
	 boolean booleanVar;
	 float floatVar;
	 double doubleVar;
	 byte byteVar;
	 String stringVar;
	 Object objectVar;
	 A aVar;
	 List<A> listVar;
	 Map<String, A> mapVar;
	 long[] longArrayVar;
	 A[] aArrayVar;
	 HashTest hashTestVar;
	 HashTest[] hashTestArrayVar;
	 
	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + ((aVar == null) ? 0 : aVar.hashCode());
		result = prime * result + (booleanVar ? 1231 : 1237);
		result = prime * result + byteVar;
		long temp;
		temp = Double.doubleToLongBits(doubleVar);
		result = prime * result + (int) (temp ^ (temp >>> 32));
		result = prime * result + Float.floatToIntBits(floatVar);
		result = prime * result + Arrays.hashCode(hashTestArrayVar);
		result = prime * result
				+ ((hashTestVar == null) ? 0 : hashTestVar.hashCode());
		result = prime * result + intVar;
		result = prime * result + ((listVar == null) ? 0 : listVar.hashCode());
		result = prime * result + Arrays.hashCode(longArrayVar);
		result = prime * result + (int) (longVar ^ (longVar >>> 32));
		result = prime * result + ((mapVar == null) ? 0 : mapVar.hashCode());
		result = prime * result
				+ ((objectVar == null) ? 0 : objectVar.hashCode());
		result = prime * result + Arrays.hashCode(aArrayVar);
		result = prime * result
				+ ((stringVar == null) ? 0 : stringVar.hashCode());
		return result;
	}
	
	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		HashTest other = (HashTest) obj;
		if (aVar == null) {
			if (other.aVar != null)
				return false;
		} else if (!aVar.equals(other.aVar))
			return false;
		if (booleanVar != other.booleanVar)
			return false;
		if (byteVar != other.byteVar)
			return false;
		if (Double.doubleToLongBits(doubleVar) != Double
				.doubleToLongBits(other.doubleVar))
			return false;
		if (Float.floatToIntBits(floatVar) != Float
				.floatToIntBits(other.floatVar))
			return false;
		if (!Arrays.equals(hashTestArrayVar, other.hashTestArrayVar))
			return false;
		if (hashTestVar == null) {
			if (other.hashTestVar != null)
				return false;
		} else if (!hashTestVar.equals(other.hashTestVar))
			return false;
		if (intVar != other.intVar)
			return false;
		if (listVar == null) {
			if (other.listVar != null)
				return false;
		} else if (!listVar.equals(other.listVar))
			return false;
		if (!Arrays.equals(longArrayVar, other.longArrayVar))
			return false;
		if (longVar != other.longVar)
			return false;
		if (mapVar == null) {
			if (other.mapVar != null)
				return false;
		} else if (!mapVar.equals(other.mapVar))
			return false;
		if (objectVar == null) {
			if (other.objectVar != null)
				return false;
		} else if (!objectVar.equals(other.objectVar))
			return false;
		if (!Arrays.equals(aArrayVar, other.aArrayVar))
			return false;
		if (stringVar == null) {
			if (other.stringVar != null)
				return false;
		} else if (!stringVar.equals(other.stringVar))
			return false;
		return true;
	}
		 
}


class A{
	
	int a;
	long b;
	String c;
	
	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + a;
		result = prime * result + (int) (b ^ (b >>> 32));
		result = prime * result + ((c == null) ? 0 : c.hashCode());
		return result;
	}
	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		A other = (A) obj;
		if (a != other.a)
			return false;
		if (b != other.b)
			return false;
		if (c == null) {
			if (other.c != null)
				return false;
		} else if (!c.equals(other.c))
			return false;
		return true;
	}
	
	
}