JAVA中equals,hashcode方法解析

概述:

  • toString 將對象以字符串形式表示,大多爲了顯示用的
  • compareto 爲了比較大小用的
  • hashcode 獲取對象hash值,只有用到HashtableHashMapHashSetLinkedHashMap等時纔要注意hashcode,其他地方hashcode無用。
  • equals 判斷對象是否相同

爲什麼要重寫hashCode方法和equals方法?

    JAVA中判斷對象是否相等按如下規則:

首先,判斷兩個對象的hashCode是否相等

如果hashcode不相等,認爲兩個對象也不相等

如果hashcode相等,則需進一步判斷equals運算是否相等

如果equals運算不相等,認爲兩個對象也不相等

如果equals運算相等,認爲兩個對象相等

    要注意的是equals()hashcode()這兩個方法都是從object類中繼承過來的,Object類中,hashcode方法返回值是如何得到請查閱其他文獻,equals方法相當於==,是比較對象的引用值是否相等。

equals詳解

    equals()方法在object類中定義如下:
public boolean equals(Object obj) {
return (this == obj);
}

    很明顯是對兩個對象的地址值進行的比較(即比較引用是否相同)。但是我們必需清楚,當String 、Math、還有Integer、Double。。。。等這些封裝類在使用equals()方法時,已經覆蓋了object類的equals()方法。比如在String類中如下:
	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;
	}

    很明顯,這是進行的內容比較,而已經不再是地址的比較。依次類推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()方法時,必須遵守的準則,如果違反會出現意想不到的結果,請大家一定要遵守。

hashcode詳解

    函數聲明:public native int hashCode();
    說明是hashcode一個本地方法,它的實現是根據本地機器相關的。當然我們可以在自己寫的類中覆蓋hashcode()方法,比如String、Integer、Double。。。。等等這些類都是覆蓋了hashcode()方法的。例如在String類中定義的hashcode()方法如下:
	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。)

hashcode與equals的關係

    equals()相等的兩個對象,hashcode()一定相等;equals()不相等的兩個對象,卻並不能證明他們的hashcode()不相等。換句話說,equals()方法不相等的兩個對象,hashcode()有可能相等。(我的理解是由於哈希碼在生成的時候產生衝突造成的)。
反過來:hashcode()不等,一定能推出equals()也不等;hashcode()相等,equals()可能相等,也可能不等。

hashcode()和equals()在hashset,hashmap,hashtable中的使用

    Hashset是繼承Set接口,Set接口又實現Collection接口,這是層次關係。那麼hashset是根據什麼原理來存取對象的呢?
    在hashset中不允許出現重複對象,元素的位置也是不確定的。在hashset中又是怎樣判定元素是否重複的呢?這就是問題的關鍵所在,經過一下午的查詢求證終於獲得了一點啓示,和大家分享一下,在java的集合中,判斷兩個對象是否相等的規則是:
    1)判斷兩個對象的hashCode是否相等
        如果不相等,認爲兩個對象也不相等,完畢
        如果相等,轉入2)
    2)判斷兩個對象用equals運算是否相等
        如果不相等,認爲兩個對象也不相等
        如果相等,認爲兩個對象相等(equals()是判斷兩個對象是否相等的關鍵)
    爲什麼是兩條準則,難道用第一條不行嗎?不行,因爲前面已經說了,hashcode()相等時,equals()方法也可能不等,所以必須用第2條準則進行限制,才能保證加入的爲非重複元素。
    比如下面的代碼:
public static void main(String args[]){
	String s1=new String("pstar");
	String s2=new String("pstar");
	System.out.println(s1==s2);//false
	System.out.println(s1.equals(s2));//true
	System.out.println(s1.hashCode());//s1.hashcode()等於s2.hashcode()
	System.out.println(s2.hashCode());
	Set hashset=new HashSet();
	hashset.add(s1);
	hashset.add(s2);
	Iterator it=hashset.iterator();
	while(it.hasNext()){
		System.out.println(it.next());
	}
}

    最後在while循環的時候只打印出了一個”zhaoxudong”。
    輸出結果爲:

false
true
-967303459
-967303459

    這是因爲String類已經重寫了equals()方法和hashcode()方法,所以在根據上面的第1.2條原則判定時,hashset認爲它們是相等的對象,進行了重複添加。
    但是看下面的程序:
public class HashSetTest {
	public static void main(String[] args) {
		HashSet hs = new HashSet();
		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 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:zhangsan
1:zhangsan
3:wangwu
2:lisi

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

    那麼怎麼解決這個問題呢?答案是:在Student類中重新hashcode()和equals()方法。
    例如:

class Student {
	int num;
	String name;

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

	public int hashCode() {
		return num * name.hashCode();
	}

	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:zhangsan
3:wangwu
2:lisi

重寫equals()和hashcode()的問題

1) 重點是重寫equals,重寫hashCode只是爲了提高效率

2) 爲什麼重點是重寫equals呢,因爲比較對象相等最終是靠equals來判斷

3)hash函數性能高,所以重寫hashcode方法在equals方法之前先過濾一次,會加速比較過程

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