在我們剛開始學習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()); } } |
如果hashCode中result的初始值爲0,那麼對象t和對象t2的hashCode值都會爲0,儘管這兩個對象不同。但如果result的值爲17,那麼計算hashCode的時候就不會忽略這些爲0的值,最後的結果t1是15699857,t2是506447
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; } |
注意,緩存屬性必須是volatile的,這樣可以在併發訪問環境中保持內存可見性。否則會產生線程安全問題。
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 )); } |
java.lang.Float的hashCode實現:
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); } |
java.lang.double的hashCode實現:
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 ); } |
然後構建的過程是這樣的,除了指定過濾的,比如transient屬性、excludeFields指定的屬性之外,會遍歷其它的類屬性,然後通過反射的方式獲取屬性值,如果屬性是數組,則會遍歷數組,否則會調用屬性的hashCode, 如果是多維數組,會去遞歸取hashCode值,對單個屬性計算hash值的代碼如下:
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 ; } |
這裏要小心,因爲它是直接調用屬性對象的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
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> [email protected] *
*/ 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(); } } |