hashCode和equals方法是Object類的相關方法,而所有的類都是直接或間接的繼承於Object類而存在的,為此,所有的類中都存在著hashCode和equals。通過翻看Object類的相關源碼,發現其hashCode的實現方式如下: 從中 ...
hashCode和equals方法是Object類的相關方法,而所有的類都是直接或間接的繼承於Object類而存在的,為此,所有的類中都存在著hashCode和equals。通過翻看Object類的相關源碼,發現其hashCode的實現方式如下:
public native int hashCode();
從中可以看出,hashCode的實現是一個本地方法,並且其返回了一個int型的值。很多人都認為在預設情況下,hashCode返回的就是對象的存儲地址,事實上這樣的看法是不全面的,確實有些JVM在實現時是直接返回對象的存儲地址的,但是在大多數的時候,其只能說是與存儲地址有一定的關聯,下麵的是HotSpot JVM中生成hash散列值的實現:
@代碼來源:http://www.cnblogs.com/dolphin0520/p/3681042.html
static inline intptr_t get_next_hash(Thread * Self, oop obj) {
intptr_t value = 0 ;
if (hashCode == 0) {
// This form uses an unguarded global Park-Miller RNG,
// so it's possible for two threads to race and generate the same RNG.
// On MP system we'll have lots of RW access to a global, so the
// mechanism induces lots of coherency traffic.
value = os::random() ;
} else
if (hashCode == 1) {
// This variation has the property of being stable (idempotent)
// between STW operations. This can be useful in some of the 1-0
// synchronization schemes.
intptr_t addrBits = intptr_t(obj) >> 3 ;
value = addrBits ^ (addrBits >> 5) ^ GVars.stwRandom ;
} else
if (hashCode == 2) {
value = 1 ; // for sensitivity testing
} else
if (hashCode == 3) {
value = ++GVars.hcSequence ;
} else
if (hashCode == 4) {
value = intptr_t(obj) ;
} else {
// Marsaglia's xor-shift scheme with thread-specific state
// This is probably the best overall implementation -- we'll
// likely make this the default in future releases.
unsigned t = Self->_hashStateX ;
t ^= (t << 11) ;
Self->_hashStateX = Self->_hashStateY ;
Self->_hashStateY = Self->_hashStateZ ;
Self->_hashStateZ = Self->_hashStateW ;
unsigned v = Self->_hashStateW ;
v = (v ^ (v >> 19)) ^ (t ^ (t >> 8)) ;
Self->_hashStateW = v ;
value = v ;
}
value &= markOopDesc::hash_mask;
if (value == 0) value = 0xBAD ;
assert (value != markOopDesc::no_hash, "invariant") ;
TEVENT (hashCode: GENERATE) ;
return value;
}
ps:該實現位於hotspot/src/share/vm/runtime/synchronizer.cpp文件下。
hashCode方法的主要作用是為了配合基於散列的集合一起正常運行,這樣的散列集合包括HashSet、HashMap以及HashTable。Hash相關的數據結構是根據對象的相關信息(可以稱為鍵),通過一定的運算規則將其散列映射到一個數值上的,為此,Hash相關的數據結構具有查找和插入的速度都較快的優點。Java中的hashCode方法就是根據一定的規則將與對象相關的信息(比如對象的存儲地址,對象的欄位等)映射成一個數值,這個數值稱作為散列值也稱為哈希值。而需要註意的一點是在一般情況下即使兩個對象的hash值相同也不能判定這兩個對象的相關信息是相同的,因為hash函數一般而言都不會是一個雙射函數,為此有碰撞產生的情況存在(即兩個不同的對象會得到相同的hash值)。為此,在比較兩個對象是否相同的時候,便需要有equals方法作為輔助了。
至於equals方法,相信學過java基礎的人都會知道,在String等類中其用於判斷兩個對象的值是否相等,至於在Object類中,其實現如下:
public boolean equals(Object obj) {
return (this == obj);
}
從中可以發現,對於Object類來說,其equals方法判斷的只是兩個對象是不是同一個對象而已,當為同一個對象的時候返回true,否則返回false。而這並不能夠實現判斷兩個對象的值是否相同的功能,通過查看String類中的equals方法的相關源碼:
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String anotherString = (String)anObject;
int n = count;
if (n == anotherString.count) {
char v1[] = value;
char v2[] = anotherString.value;
int i = offset;
int j = anotherString.offset;
while (n-- != 0) {
if (v1[i++] != v2[j++])
return false;
}
return true;
}
}
return false;
}
我們可以發現,其重寫了Object類的equals方法,使得其可以對兩個對象的值是否一致進行判斷。
至此,我們對於hashCode和equals方法的作用有了一定的瞭解,hashCode方法的作用是根據對象的相關信息(對象的欄位值,對象的存儲地址)通過一定的運算規則獲得一個int型值,該int值在一定程度上反應了對象的有關信息。而equals方法的作用是用於判斷兩個對象的相關的指定信息是否是一樣的。在Hash相關的數據結構中通過結合使用hashCode方法以及equals方法來提高插入和查找效率。
例如,java.util.HashMap的中put方法的具體實現:
public V put(K key, V value) {
if (key == null)
return putForNullKey(value);
int hash = hash(key.hashCode());
int i = indexFor(hash, table.length);
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
modCount++;
addEntry(hash, key, value, i);
return null;
}
一般而言,對於使用Hash相關的數據結構,其會先通過調用對象的hashCode方法獲取該對象的hash值,縮小了查找範圍之後,再通過調用equals方法進行“精細”的查找,以縮短查找時間和範圍,提高查找效率。
通過以上的分析,我們需要註意到如下的幾點:
- 不同的對象可能會生成相同的hash值。為此,不能通過hash值來判斷兩個對象是否相等,但是可以通過hash值來判斷兩個對象的不相等,即hash值相等的兩個對象,其不一定是相等的,而hash值不相等的兩個對象,其一定是不相等的。
- equals方法返回結果為ture的一定是相等的對象
由以上的這兩點,我們可以推出如下的四點內容:
- 如果調用equals方法得到的結果為true,則兩個對象的hashcode值必定相等
- 如果equals方法得到的結果為false,則兩個對象的hashcode值不一定不同
- 如果兩個對象的hashcode值不等,則equals方法得到的結果必定為false
- 如果兩個對象的hashcode值相等,則equals方法得到的結果未知。
為此,我們可以知道,在重寫equals方法的時候,必須要重寫了hashCode方法。
在重寫equals方法和hashCode方法的時候,需要註意以下幾點:
- 在程式執行期間,只要equals方法的比較操作用到的信息沒有被修改,那麼對這同一個對象調用多次,hashCode方法必須始終如一地返回同一個整數。
- 如果兩個對象根據equals方法比較是相等的,那麼調用兩個對象的hashCode方法必須返回相同的整數結果。
- 如果兩個對象根據equals方法比較是不等的,則hashCode方法不一定得返回不同的整數。
------------------------------------------->摘自《Effective Java》
重寫equals和hashCode方法的一個例子:
public class HashCodeTest
{
private String name="小小";
private int age=12;
@Override
public int hashCode()
{
return age*37+name.hashCode();
}
@Override
public boolean equals(Object other)
{
if(other==this)
return true;
if(other==null)
return false;
if(other instanceof HashCodeTest)
{
HashCodeTest t=(HashCodeTest) other;
return this.age==t.age&&this.name.equals(t.name);
}
return false;
}
}
註意的一點是:
在設計hashCode方法和equals方法的時候,如果對象中的數據易變,則最好在equals方法和hashCode方法中不要依賴於該欄位