Java中==、equals()和hashCode()的比較分析

1、前言

在Java語言中,==equals()hashCode()這三個方法都和對象的比較有關,但這三個方法各有什麼用處,也就是說爲什麼要設計三種對象的比較方法呢?

2、關於==

==設計的目的就是爲比較兩個對象是否是同一個對象。比較對象的相等不僅要比較對象內容相等,還要比較對象引用地址是否相等

對於基本數據類型而言,比較就是判斷這兩個數值是否相等,(基本數據類型沒有方法),不存在equals()和hashCode()比較的問題,下面的討論都是針對引用數據類型展開的。

對於引用對象而言,比較兩個引用變量的引用的是否是統一個對象,即比較的是兩個引用存儲中的對象地址是不是一樣

3、關於equals()

==比較的是兩個對象是否是同一個對象,這並不能滿足很多需求。有時候當兩個對象不==的時候,我們仍然會認爲兩者是“相等”的,比如對於String對象,當兩個對象的字符串序列是一致的,我們就認爲他們是“相等”的。對於這樣的需求,需要equals()來實現。對於有這種需求的對象的類,重寫其equals()方法便可,具體的“相等”邏輯可以根據需要自己定義。

equals()和hashCode()都是從Object類中繼承而來的,而Object中equals()默認實現的是比較兩個對象是否==,即默認的equals()和==的效果是一樣的。equals()方法在Object類中定義如下:

  1. public boolean equals(Object obj) {   
  2. return (this == obj);   
  3. }   
public boolean equals(Object obj) { 
return (this == obj); 
} 
很明顯是對兩個對象的地址值進行的比較(即比較引用是否相同)。但是我們必需清楚,當String 、Math、還有Integer、Double。。。。等這些封裝類在使用equals()方法時,已經覆蓋了object類的equals()方法。比如在String類中如下:

  1. public boolean equals(Object anObject) {  
  2.     if (this == anObject) {  
  3.         return true;  
  4.     }  
  5.     if (anObject instanceof String) {  
  6.         String anotherString = (String)anObject;  
  7.         int n = count;  
  8.         if (n == anotherString.count) {  
  9.         char v1[] = value;  
  10.         char v2[] = anotherString.value;  
  11.         int i = offset;  
  12.         int j = anotherString.offset;  
  13.         while (n-- != 0) {  
  14.             if (v1[i++] != v2[j++])  
  15.             return false;  
  16.         }  
  17.         return true;  
  18.         }  
  19.     }  
  20.     return false;  
  21.     }  
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;
    }
String類中的equals()方法很明顯是僅僅進行了對象內容的比較,而沒有比較對象存儲地址。同理我們可知,Double,Integer,Math等等這些類的equals()方法都是被重寫了的,從而比較的都是內容的值。

我們還應該注意,Java語言對equals()的要求如下,這些要求是必須遵循的: 
• 對稱性:如果x.equals(y)返回是“true”,那麼y.equals(x)也應該返回是“true”。 
• 反射性:x.equals(x)必須返回是“true”。 
• 類推性:如果x.equals(y)返回是“true”,而且y.equals(z)返回是“true”,那麼z.equals(x)也應該返回是“true”。 
• 還有一致性:如果x.equals(y)返回是“true”,只要x和y內容一直不變,不管你重複x.equals(y)多少次,返回都是“true”。 
• 任何情況下,x.equals(null),永遠返回是“false”;x.equals(和x不同類型的對象)永遠返回是“false”。 
以上這五點是重寫equals()方法時,必須遵守的準則,如果違反會出現意想不到的結果,請大家一定要遵守。

4、關於hashCode()

hashCode()方法返回的是一個數值,稱之爲hashCode。hashCode()方法在Object類中的定義如下:

  1. public native int hashCode();  
public native int hashCode();
這是一個本地方法,其實現根據本地機器相關。

當然我們可以再自定義類中覆蓋hashCode()方法,比如String、Integer、Double等等這些類都是覆蓋了hashCode()方法的。例如在String類中定義的hashcode()方法如下:

  1. public int hashCode() {   
  2. int h = hash;   
  3. if (h == 0) {   
  4.     int off = offset;   
  5.     char val[] = value;   
  6.     int len = count;   
  7.   
  8.             for (int i = 0; i < len; i++) {   
  9.                 h = 31*h + val[off++];   
  10.             }   
  11.             hash = h;   
  12.         }   
  13.         return h;   
  14. }   
public int hashCode() { 
int h = hash; 
if (h == 0) { 
    int off = offset; 
    char val[] = value; 
    int len = count; 

            for (int i = 0; i < len; i++) { 
                h = 31*h + val[off++]; 
            } 
            hash = h; 
        } 
        return h; 
} 
解釋一下這個程序(String的API中寫到): 
s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1] 
使用 int 算法,這裏 s[i] 是字符串的第 i 個字符,n 是字符串的長度,^ 表示求冪。(空字符串的哈希碼爲 0。) 

5、equals()和hashCode()千絲萬縷的關係

hashCode()方法常被設計用來提高性能,其兩者的關係在於:①若兩個對象相等(equals),那麼這兩個對象一定有相同的哈希值(hashCode);②若兩個對象的哈希值相同,但這兩個對象並不一定相等。

上述兩點說明,通過equals()方法判斷兩個對象相等,那麼這兩個對象的hashCode()方法返回的hashCode一定相等;但equals()方法不相等的兩個對象,卻不一定他們的hashCode()不相等,也就是說,equals()方法不相等的兩個對象,hashcode()有可能相等。(我的理解是由於哈希碼在生成的時候產生衝突造成的)。反之,hashCode()不相等,一定能推出equals()也不等;hashCode()相等,並不能說明equals()相等或不等。

解釋下上述關係的使用範圍,我的理解是在object、String等類中都能使用。在object類中,hashCode()方法是本地方法,返回的是對象的地址值,而object類中的equals()方法比較的也是兩個對象的地址值,如果equals()相等,說明兩個對象地址值也相等,當然hashCode()也就相等了;在String類中,equals()返回的是兩個對象內容的比較,當兩個對象內容相等時,hashCode()方法根據String類的重寫(前面關於hashCode()已經說明)代碼的分析,也可知道hashCode()返回結果也會相等。以此類推,可以知道Integer、Double等封裝類中經過重寫的equals()和hashCode()方法也同樣適合於這個原則。當然沒有經過重寫的類,在繼承了object類的equals()和hashCode()方法後,也會遵守這個原則。 

6、equals()和hashCode()方法在HashSet、HashMap、Hashtable中的應用

以HashSet集合爲例具體說明:

Hashset是繼承Set接口,Set接口又實現Collection接口,這是層次關係。那麼hashset是根據什麼原理來存取對象的呢? 
在hashset中不允許出現重複對象,元素的位置也是不確定的。在hashset中又是怎樣判定元素是否重複的呢?這就是問題的關鍵所在,在java的集合中,判斷兩個對象是否相等的規則是: 
1) 判斷兩個對象的hashCode是否相等。如果不相等,認爲兩個對象也不相等,完畢; 如果相等,轉入2) 
(這一點只是爲了提高存儲效率而要求的,其實理論上沒有也可以,但如果沒有,實際使用時效率會大大降低,所以我們這裏將其做爲必需的。後面會重點講到這個問題。) 
2) 判斷兩個對象用equals運算是否相等 。如果不相等,認爲兩個對象也不相等;如果相等,認爲兩個對象相等(equals()是判斷兩個對象是否相等的關鍵) 
爲什麼是兩條準則,難道用第一條不行嗎?不行,因爲前面已經說了,hashCode()相等時,equals()方法也可能不等,所以必須用第2條準則進行限制,才能保證加入的爲非重複元素。 

參看程序1:

  1. String s1 = new String("zhaoxudong");  
  2.         String s2 = new String("zhaoxudong");  
  3.         System.out.println(s1 == s2);// false  
  4.         System.out.println(s1.equals(s2));// true  
  5.         System.out.println(s1.hashCode());  
  6.         System.out.println(s2.hashCode());// s1.hashcode()等於s2.hashcode()  
  7.         Set<String> hashset = new HashSet<String>();  
  8.         hashset.add(s1);  
  9.         hashset.add(s2);  
  10.         /* 實質上在添加s1,s2時,運用上面說到的兩點準則, 
  11.          * 可以知道hashset認爲s1和s2是相等的,是在添加重複元素, 
  12.          * 所以讓s2覆蓋了s1; */  
  13.         Iterator<String> it = hashset.iterator();  
  14.         while (it.hasNext()) {  
  15.             System.out.println(it.next());  
  16.         }  
String s1 = new String("zhaoxudong");
		String s2 = new String("zhaoxudong");
		System.out.println(s1 == s2);// false
		System.out.println(s1.equals(s2));// true
		System.out.println(s1.hashCode());
		System.out.println(s2.hashCode());// s1.hashcode()等於s2.hashcode()
		Set<String> hashset = new HashSet<String>();
		hashset.add(s1);
		hashset.add(s2);
		/* 實質上在添加s1,s2時,運用上面說到的兩點準則,
		 * 可以知道hashset認爲s1和s2是相等的,是在添加重複元素,
		 * 所以讓s2覆蓋了s1; */
		Iterator<String> it = hashset.iterator();
		while (it.hasNext()) {
			System.out.println(it.next());
		}
輸出結果:

  1. false  
  2. true  
  3. -967303459  
  4. -967303459  
  5. zhaoxudong  
false
true
-967303459
-967303459
zhaoxudong
分析:這是因爲String類重寫了equals()方法和hashCode()方法,所以根據上述兩條原則進行判定時,HashSet認爲s1和s2是兩個相等的對象,故而不會進行重複添加,s2會直接覆蓋掉s1。

參看程序2:

  1. public class Test {  
  2.     public static void main(String[] args) {  
  3.         HashSet<Student> hs = new HashSet<Student>();  
  4.         hs.add(new Student(1"zhangsan"));  
  5.         hs.add(new Student(2"lisi"));  
  6.         hs.add(new Student(3"wangwu"));  
  7.         hs.add(new Student(1"zhangsan"));  
  8.   
  9.         Iterator<Student> it = hs.iterator();  
  10.         while (it.hasNext()) {  
  11.             System.out.println(it.next());  
  12.         }  
  13.     }  
  14. }  
  15.   
  16. class Student {  
  17.     int num;  
  18.     String name;  
  19.   
  20.     Student(int num, String name) {  
  21.         this.num = num;  
  22.         this.name = name;  
  23.     }  
  24.   
  25.     public String toString() {  
  26.         return num + ":" + name;  
  27.     }  
  28. }  
public class Test {
	public static void main(String[] args) {
		HashSet<Student> hs = new HashSet<Student>();
		hs.add(new Student(1, "zhangsan"));
		hs.add(new Student(2, "lisi"));
		hs.add(new Student(3, "wangwu"));
		hs.add(new Student(1, "zhangsan"));

		Iterator<Student> it = hs.iterator();
		while (it.hasNext()) {
			System.out.println(it.next());
		}
	}
}

class Student {
	int num;
	String name;

	Student(int num, String name) {
		this.num = num;
		this.name = name;
	}

	public String toString() {
		return num + ":" + name;
	}
}
輸出結果:

  1. 2:lisi  
  2. 1:zhangsan  
  3. 1:zhangsan  
  4. 3:wangwu  
2:lisi
1:zhangsan
1:zhangsan
3:wangwu
問題出現了,爲什麼hashset添加了相等的元素呢,這是不是和hashset的原則違背了呢?回答是:沒有 
因爲在根據hashcode()對兩次建立的new Student(1,"zhangsan")對象進行比較時,生成的是不同的哈希碼值,所以hashset把他當作不同的對象對待了,當然此時的equals()方法返回的值也不等(這個不用解釋了吧)。那麼爲什麼會生成不同的哈希碼值呢?上面我們在比較s1和s2的時候不是生成了同樣的哈希碼嗎?原因就在於我們自己寫的Student類並沒有重新自己的hashcode()和equals()方法,所以在比較時,是繼承的object類中的hashcode()方法,呵呵,各位還記得object類中的hashcode()方法比較的是什麼吧!! 
它是一個本地方法,比較的是對象的地址(引用地址),使用new方法創建對象,兩次生成的當然是不同的對象了(這個大家都能理解吧。。。),造成的結果就是兩個對象的hashcode()返回的值不一樣。所以根據第一個準則,hashset會把它們當作不同的對象對待,自然也用不着第二個準則進行判定了。那麼怎麼解決這個問題呢?? 
答案是:在Student類中重新hashcode()和equals()方法。 

參考代碼:

  1. class Student {  
  2.     int num;  
  3.     String name;  
  4.   
  5.     Student(int num, String name) {  
  6.         this.num = num;  
  7.         this.name = name;  
  8.     }  
  9.     /*hashCode()方法重寫*/  
  10.     public int hashCode() {  
  11.         return num * name.hashCode();  
  12.     }  
  13.     /*這裏用num和name作爲條件來進行比較, 
  14.      * 若num和name都相同的對象就被視爲相等的對象*/  
  15.     public boolean equals(Object o) {  
  16.         Student s = (Student) o;  
  17.         return num == s.num && name.equals(s.name);  
  18.     }  
  19.   
  20.     public String toString() {  
  21.         return num + ":" + name;  
  22.     }  
  23. }  
class Student {
	int num;
	String name;

	Student(int num, String name) {
		this.num = num;
		this.name = name;
	}
	/*hashCode()方法重寫*/
	public int hashCode() {
		return num * name.hashCode();
	}
	/*這裏用num和name作爲條件來進行比較,
	 * 若num和name都相同的對象就被視爲相等的對象*/
	public boolean equals(Object o) {
		Student s = (Student) o;
		return num == s.num && name.equals(s.name);
	}

	public String toString() {
		return num + ":" + name;
	}
}
根據重寫的方法,即便兩次調用了new Student(1,"zhangsan"),我們在獲得對象的哈希碼時,根據重寫的方法hashcode(),獲得的哈希碼肯定是一樣的(這一點應該沒有疑問吧)。 
當然根據equals()方法我們也可判斷是相同的。所以在向hashset集合中添加時把它們當作重複元素看待了。所以運行修改後的程序時,我們會發現運行結果是:

  1. 1:zhangsan  
  2. 3:wangwu  
  3. 2:lisi  
1:zhangsan
3:wangwu
2:lisi
通過結果可知,重複的元素問題已經消除。

關於在hibernate的pojo類中,重新equals()和hashcode()的問題: 
1) 重點是equals,重寫hashCode只是技術要求(爲了提高效率) 
2) 爲什麼要重寫equals呢,因爲在java的集合框架中,是通過equals來判斷兩個對象是否相等的 
3) 在hibernate中,經常使用set集合來保存相關對象,而set集合是不允許重複的。我們再來談談前面提到在向hashset集合中添加元素時,怎樣判斷對象是否相同的準則,前面說了兩條,其實只要重寫equals()這一條也可以。 
但當hashset中元素比較多時,或者是重寫的equals()方法比較複雜時,我們只用equals()方法進行比較判斷,效率也會非常低,所以引入了hashcode()這個方法,只是爲了提高效率,但是我覺得這是非常有必要的(所以我們在前面以兩條準則來進行hashset的元素是否重複的判斷)。 

發佈了48 篇原創文章 · 獲贊 13 · 訪問量 8萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章