在我们刚开始学习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》中推荐的实现方式
1
2
3
4
5
6
7
8
9
10
11
12
|
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;
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
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());
}
}
|
1
2
3
4
5
6
7
8
9
10
11
12
|
private
volatile
int
hashCode =
0
;
@Override
public
int
hashCode() {
int
result = hashCode;
if
(result ==
0
) {
...
//计算过程
}
return
result;
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
/**
* 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
));
}
|
1
2
3
4
5
6
7
8
9
10
11
12
|
/**
* 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);
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
/**
* 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)
其余的版本只是提供不同的默认参数,从而简化了构建的过程。比如:
1
2
3
|
public
static
int
reflectionHashCode(Object object) {
return
reflectionHashCode(
17
,
37
, object,
false
,
null
,
null
);
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
|
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
;
}
|
然后剩下的计算过程,同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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
|
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;
}
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
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();
}
|
from:http://my.oschina.net/chihz/blog/56256