一、初識equals()和hashCode()方法
1、首先需要明確知道的一點是:hashCode()方法和equals()方法是在Object類中就已經定義了的,所以在java中定義的任何類都會有這兩個方法。原始的equals()方法用來比較兩個對象的地址值,而原始的hashCode()方法用來返回其所在對象的物理地址,下面來看一下在Object中的定義:
public boolean equals(Object obj)
{
return (this == obj); //比較的是兩個對象的地址是否相同
}
hashCode:
public native int hashCode();//對應一個本地實現
2、如果定義了一個類A,只要它沒有重寫這兩個方法,這兩個方法的意義就是如上面所述。請看下面的示例:
示例一
class Rect
{
int x;
int y;
public Rect(int x,int y)
{
this.x = x;
this.y = y;
}
}
public class Temp
{
public static void main(String[] args)
{
Rect r1 = new Rect(1,2);
Rect r2 = new Rect(1,2);
System.out.println("r1.equals(r2)爲:" + r1.equals(r2));
System.out.println("r1的hashCode爲:" + r1.hashCode());
System.out.println("r2的hashCode爲:" + r2.hashCode());
}
}
運行結果:
r1.equals(r2)爲:false //二者內存地址不同,即不是同一對象
r1的hashCode爲:319454176
r2的hashCode爲:357218532
二、equals()和hashCode()的重寫
前面說到,因爲這兩個方法都是在Object類中定義,所以java中定義的所有類都會有這兩個方法,並且根據實際需要可以重寫這兩個方法,當方法被重寫後,他們所代表的意義就會發生變化,看下面的代碼:
示例二
public static void main(String args[])
{
String s1=new String("abc");
String s2=new String("abc");
System.out.println(s1.equals(s2));//輸出true,因爲String類重寫了equals()方法,雖然s1和s2此時指向的地址空間不同,它比較的不再是地址而是String的內容, 此時s1和s2都是"abc",故返回true
System.out.println(s1.hashCode());
System.out.println(s2.hashCode());//你會發現s1和s2的hashCode相同,因爲String同樣重寫了hashCode()方法,返回的不再是地址,而是根據具體的字符串算出的一個值
}
運行結果:
true
96354
96354
三、equals()和hashCode()方法是怎樣被重寫的
1、我們來看一下String類中這兩個方法的定義:
equals()方法:
public boolean equals(Object anObject)
{
if (this == anObject) //首先比較兩個對象地址;若相同,必是同一對象
{
return true;
}
if (anObject instanceof String)
{
String anotherString = (String) anObject;
int n = value.length;
if (n == anotherString.value.length)
{
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
while (n-- != 0)
{
if (v1[i] != v2[i]) //比較String中每個字符
return false;
i++;
}
return true;
}
}
return false;
}
}
hashCode()方法:
public int hashCode() {
int h = hash;
if (h == 0 && value.length > 0) {
char val[] = value;
for (int i = 0; i < value.length; i++) {
h = 31 * h + val[i]; //根據String中每個字符確定hashCode;若兩個字符串的內容相同,hashCode便相同
}
hash = h;
}
return h;
}
2、Integer中兩方法的定義:
equals()方法:
public boolean equals(Object obj) {
if (obj instanceof Integer) {
return value == ((Integer)obj).intValue();
}
return false;
}
hashCode方法:
public int hashCode() {
return value;
}
3、小結:
8中基本數據類型的hashCode方法直接就是數值大小,String的hashCode是根據字符串內容計算得出的。
8中基本數據類型的equals方法直接比較值,String的equals方法比較的是String的內容。
四、HASH算法簡介:
歷經比較的關鍵字的個數。hash表就是爲了實現在記錄的關鍵字和其存 儲位置之間建立一個確定的函數關係f,即將關鍵字爲key的記錄存儲在f(key)的位置上,這樣在記錄中查找關鍵字時,只需要根據該關鍵字先計算出其在整個記錄中的位置便可找到,不必挨個遍歷了。將hash算法應用到HashSet集合中提高在集合中查找元素的效率。這種方式將集合分成若干個存儲區域,每個對象存儲一個哈希碼,然後將哈希碼分組,每組對應一個存儲區域,根據一個對象的哈希碼就可以確定該對象的存儲區域(採用hash函數來顯示,例如:假設有n個存儲區域,利用哈希碼與n相除的餘數來確定對象存儲區域,若存儲區域相同,便進行再散列)。由於記錄在線性表中的存儲位置是隨機的,和關鍵字無關,因此在查找關鍵字等於給定值的記錄時,需將給定值和線性表中的關鍵字逐個進行比較,查找的效率基於歷經
Object類中定義了一個hashCode方法來返回每個對象的哈希碼,然後根據哈希碼找到相應的存儲區域,最後取得該區域內的每個元素與該對象就行equals比較,這樣
就不用遍歷集合中的所有元素就可以得出結論,可見HashSet具有很好的檢索性能。但是,HashSet存儲對象的效率略低一些,因爲向HashSet中添加一個元素時,要先計算該對象的哈希碼,並根據哈希碼確定該對象要存儲的存儲區域。爲了保證一個類的每個對象都能在HashSet中正常存儲,要求該類的實例對象在equals方法比較相同時,它們的hashCode必須也是相同的。即:obj1.equals(obj2)爲true的話,obj1.hashCode()==obj2.hashCode()也爲true。換句話說,當我們重寫一個類的equals方法時,必須重寫它的hashCode方法。如果不重寫hashCode方法的話,Object對象中的hashCode方法返回的始終是一個對象的hash地址,而這個地址是永遠不相等的,這時,即使重寫了equals方法也是沒用的,因爲如果兩個對象的hashCode不相等的話,這兩個對象就會被分到不同的存儲區域去了,自然就沒機會用equals方法進行比較了。
五、equals()和hashCode()方法的應用
HashSet集合:
Hashset是java中一個非常重要的集合類,Hashset中不能有重複的元素,當一個元素添加到集合中的時候,Hashset判斷元素是否重複的依據是這樣的:
1)判斷兩個對象的hashCode是否相等
如果不相等,認爲兩個對象也不相等,完畢
如果相等,轉入2)
(這一點只是爲了提高存儲效率而要求的,其實理論上沒有也可以,但如果沒有,實際使用時效率會大大降低,所以我們這裏將其做爲必需的)
2)判斷兩個對象用equals運算是否相等
如果不相等,認爲兩個對象也不相等
如果相等,認爲兩個對象相等(equals()是判斷兩個對象是否相等的關鍵)
爲什麼是兩條準則,難道用第一條不行嗎?不行,因爲前面已經說了,hashcode()相等時,equals()方法也可能不等,所以必須用第2條準則進行限制,才能保證加入的爲非重複元素。
示例三
class RectObject
{
int x;
int y;
public RectObject(int x,int y)
{
this.x = x;
this.y = y;
}
@Override
public int hashCode()
{
final int prime = 31;
int result = 1;
result = prime * result + x;
result = prime * result + y;
return result;
}
@Override
public boolean equals(Object obj)
{
if(this == obj)
return true;
if(obj == null)
return false;
if(this.getClass() != obj.getClass())
return false;
final RectObject other = (RectObject)obj;
if(this.x != other.x)
return false;
if(this.y != other.y)
return false;
return true;
}
}
public class Test
{
public static void main(String[] args)
{
HashSet<RectObject> hashSet = new HashSet<> ();
RectObject rect1 = new RectObject(1, 2);
RectObject rect2 = new RectObject(3, 4);
RectObject rect3 = new RectObject(1, 2);
hashSet.add(rect1);
hashSet.add(rect2);
hashSet.add(rect3);
hashSet.add(rect1);
System.out.println("當前HashSet中元素個數:" + hashSet.size());
}
}
運行結果:當前HashSet中元素個數:2
分析:
我們重寫了Object中的hashCode和equals方法,當兩個對象的x、y值都相等時,它們的hashCode值是相等的,並且equals比較的結果爲true。
先添加rect1到HashSet中;在添加rect2時,由於hashCode不等,因而可以添加;添加rect3時,rect3與rect1的hashCode值相等,因而進行equals的比較,爲true,不會添加;在添加rect4時,由於與rect1相同,不會添加。
示例四(即不重寫hashCode方法)
class RectObject
{
int x;
int y;
public RectObject(int x,int y)
{
this.x = x;
this.y = y;
}
// @Override
// public int hashCode()
// {
// final int prime = 31;
// int result = 1;
// result = prime * result + x;
// result = prime * result + y;
// return result;
// }
@Override
public boolean equals(Object obj)
{
if(this == obj)
return true;
if(obj == null)
return false;
if(this.getClass() != obj.getClass())
return false;
final RectObject other = (RectObject)obj;
if(this.x != other.x)
return false;
if(this.y != other.y)
return false;
return true;
}
}
public class Test
{
public static void main(String[] args)
{
HashSet<RectObject> hashSet = new HashSet<> ();
RectObject rect1 = new RectObject(1, 2);
RectObject rect2 = new RectObject(3, 4);
RectObject rect3 = new RectObject(1, 2);
hashSet.add(rect1);
hashSet.add(rect2);
hashSet.add(rect3);
hashSet.add(rect1);
System.out.println("當前HashSet中元素個數:" + hashSet.size());
}
}
運行結果:
當前HashSet中元素個數:3
分析:先將rect1添加至HashSet中;添加rect2,rect3時,三者的hashCode均不相同,因而全部添加至集合中;因rect1 == rect1,不會添加;因而集合中有rect1、rect2、rect3三個對象。示例五(將重寫的equals方法改爲直接返回false)
class RectObject
{
int x;
int y;
public RectObject(int x,int y)
{
this.x = x;
this.y = y;
}
@Override
public int hashCode()
{
final int prime = 31;
int result = 1;
result = prime * result + x;
result = prime * result + y;
return result;
}
@Override
public boolean equals(Object obj)
{
// if(this == obj)
// return true;
// if(obj == null)
// return false;
// if(this.getClass() != obj.getClass())
// return false;
// final RectObject other = (RectObject)obj;
// if(this.x != other.x)
// return false;
// if(this.y != other.y)
// return false;
// return true;
return false;
}
}
public class Test
{
public static void main(String[] args)
{
HashSet<RectObject> hashSet = new HashSet<> ();
RectObject rect1 = new RectObject(1, 2);
RectObject rect2 = new RectObject(3, 4);
RectObject rect3 = new RectObject(1, 2);
hashSet.add(rect1);
hashSet.add(rect2);
hashSet.add(rect3);
hashSet.add(rect1);
System.out.println("當前HashSet中元素個數:" + hashSet.size());
}
}
運行結果:
當前HashSet中元素個數:3
分析:首先添加rect1到集合中;比較rect2與rect1的hashCode不同,將rect2添加至集合;rect3與rect1的hashCode相同,equals比較爲false,將rect3添加至集合;然後是rect1,rect1與rect1的hashCode相同,equals比較爲false,所以應該可以添加第二個rect1到集合中,結果應爲4,但事實上卻是3,下面來分析:
先看HashSet中add方法源碼:
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
HashSet時基於HashMap實現的,我們來看HashMap中put的源碼
public V put(K key, V value) {
if (table == EMPTY_TABLE) {
inflateTable(threshold);
}
if (key == null)
return putForNullKey(value);
int hash = hash(key);
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;
}
主要看if判斷條件:
顯示判斷兩對象hashCode是否相等,不等的話,直接跳過後面的判斷;相等的話,再來比較兩個對象是否相等或者這兩個對象的equals方法,因爲是或運算,只要一個成立即可。這樣,我們剛纔在放最後一個rect1時,由於rect1 == rect1,所以最後一個rect1並沒有放進去,所以集合大小爲3。
參考文檔:http://blog.csdn.net/jiangwei0910410003/article/details/22739953