java.lang.Object類中有兩個很是重要的方法:html
1
2
|
public
boolean
equals(Object obj)
public
int
hashCode()
|
Object
類是類繼承結構的基礎,因此是每個類的父類。全部的對象,包括數組,都實現了在Object
類中定義的方法。java
equals()
方法是用來判斷其餘的對象是否和該對象相等.git
equals()方法在object類中定義以下: 程序員
public boolean equals(Object obj) { return (this == obj); }
很明顯是對兩個對象的地址值進行的比較(即比較引用是否相同)。可是咱們知道,String 、Math、Integer、Double等這些封裝類在使用equals()方法時,已經覆蓋了object類的equals()方法。github
好比在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; }
很明顯,這是進行的內容比較,而已經再也不是地址的比較。依次類推Math、Integer、Double等這些類都是重寫了equals()方法的,從而進行的是內容的比較。固然,基本類型是進行值的比較。api
它的性質有:數組
自反性(reflexive)。對於任意不爲null
的引用值x,x.equals(x)
必定是true
。數據結構
對稱性(symmetric)。對於任意不爲null
的引用值x
和y
,當且僅當x.equals(y)
是true
時,y.equals(x)
也是true
。oracle
傳遞性(transitive)。對於任意不爲null
的引用值x
、y
和z
,若是x.equals(y)
是true
,同時y.equals(z)
是true
,那麼x.equals(z)
必定是true
。
一致性(consistent)。對於任意不爲null
的引用值x
和y
,若是用於equals比較的對象信息沒有被修改的話,屢次調用時x.equals(y)
要麼一致地返回true
要麼一致地返回false
。
對於任意不爲null
的引用值x
,x.equals(null)
返回false
。
對於Object
類來講,equals()
方法在對象上實現的是差異可能性最大的等價關係,即,對於任意非null
的引用值x
和y
,當且僅當x
和y
引用的是同一個對象,該方法纔會返回true
。
須要注意的是當equals()
方法被override時,hashCode()
也要被override。按照通常hashCode()
方法的實現來講,相等的對象,它們的hash code必定相等。
hashCode()
方法給對象返回一個hash code值。這個方法被用於hash tables,例如HashMap。
它的性質是:
在一個Java應用的執行期間,若是一個對象提供給equals作比較的信息沒有被修改的話,該對象屢次調用hashCode()
方法,該方法必須始終如一返回同一個integer。
若是兩個對象根據equals(Object)
方法是相等的,那麼調用兩者各自的hashCode()
方法必須產生同一個integer結果。
並不要求根據equals(java.lang.Object)
方法不相等的兩個對象,調用兩者各自的hashCode()
方法必須產生不一樣的integer結果。然而,程序員應該意識到對於不一樣的對象產生不一樣的integer結果,有可能會提升hash table的性能。
大量的實踐代表,由Object
類定義的hashCode()
方法對於不一樣的對象返回不一樣的integer。
在object類中,hashCode定義以下:
public native int 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的做用,必需要先知道Java中的集合。
總的來講,Java中的集合(Collection)有兩類,一類是List,再有一類是Set。前者集合內的元素是有序的,元素能夠重複;後者元素無序,但元素不可重複。這裏就引出一個問題:要想保證元素不重複,可兩個元素是否重複應該依據什麼來判斷呢?
這就是Object.equals方法了。可是,若是每增長一個元素就檢查一次,那麼當元素不少時,後添加到集合中的元素比較的次數就很是多了。也就是說,若是集合中如今已經有1000個元素,那麼第1001個元素加入集合時,它就要調用1000次equals方法。這顯然會大大下降效率。
因而,Java採用了哈希表的原理。哈希(Hash)其實是我的名,因爲他提出一哈希算法的概念,因此就以他的名字命名了。哈希算法也稱爲散列算法,是將數據依特定算法直接指定到一個地址上,初學者能夠簡單理解,hashCode方法實際上返回的就是對象存儲的物理地址(實際可能並非)。
這樣一來,當集合要添加新的元素時,先調用這個元素的hashCode方法,就一會兒能定位到它應該放置的物理位置上。若是這個位置上沒有元素,它就能夠直接存儲在這個位置上,不用再進行任何比較了;若是這個位置上已經有元素了,就調用它的equals方法與新元素進行比較,相同的話就不存了,不相同就散列其它的地址。因此這裏存在一個衝突解決的問題。這樣一來實際調用equals方法的次數就大大下降了,幾乎只須要一兩次。
簡而言之,在集合查找時,hashcode能大大下降對象比較次數,提升查找效率!
Java對象的eqauls方法和hashCode方法是這樣規定的:
一、相等(相同)的對象必須具備相等的哈希碼(或者散列碼)。
二、若是兩個對象的hashCode相同,它們並不必定相同。
如下是Object對象API關於equal方法和hashCode方法的說明:
equals(Object)
method, then calling the hashCode
method on each of the two objects must produce the same integer result.equals(java.lang.Object)
method, then calling the hashCode
method on each of the two objects must produce distinct integer results. However, the programmer should be aware that producing distinct integer results for unequal objects may improve the performance of hash tables.關於第一點,相等(相同)的對象必須具備相等的哈希碼(或者散列碼),爲何?
想象一下,假如兩個Java對象A和B,A和B相等(eqauls結果爲true),但A和B的哈希碼不一樣,則A和B存入HashMap時的哈希碼計算獲得的HashMap內部數組位置索引可能不一樣,那麼A和B頗有可能容許同時存入HashMap,顯然相等/相同的元素是不容許同時存入HashMap,HashMap不容許存放重複元素。
關於第二點,兩個對象的hashCode相同,它們並不必定相同
也就是說,不一樣對象的hashCode可能相同;假如兩個Java對象A和B,A和B不相等(eqauls結果爲false),但A和B的哈希碼相等,將A和B都存入HashMap時會發生哈希衝突,也就是A和B存放在HashMap內部數組的位置索引相同這時HashMap會在該位置創建一個連接表,將A和B串起來放在該位置,顯然,該狀況不違反HashMap的使用原則,是容許的。固然,哈希衝突越少越好,儘可能採用好的哈希算法以免哈希衝突。
因此,Java對於eqauls方法和hashCode方法是這樣規定的:
1.若是兩個對象相同,那麼它們的hashCode值必定要相同;
2.若是兩個對象的hashCode相同,它們並不必定相同(這裏說的對象相同指的是用eqauls方法比較)。
如不按要求去作了,會發現相同的對象能夠出如今Set集合中,同時,增長新元素的效率會大大降低。
3.equals()相等的兩個對象,hashcode()必定相等;equals()不相等的兩個對象,卻並不能證實他們的hashcode()不相等。換句話說,equals()方法不相等的兩個對象,hashcode()有可能相等(個人理解是因爲哈希碼在生成的時候產生衝突形成的)。反過來,hashcode()不等,必定能推出equals()也不等;hashcode()相等,equals()可能相等,也可能不等。
在object類中,hashcode()方法是本地方法,返回的是對象的地址值,而object類中的equals()方法比較的也是兩個對象的地址值,若是equals()相等,說明兩個對象地址值也相等,固然hashcode()也就相等了;在String類中,equals()返回的是兩個對象內容的比較,當兩個對象內容相等時,Hashcode()方法根據String類的重寫代碼的分析,也可知道hashcode()返回結果也會相等。以此類推,能夠知道Integer、Double等封裝類中通過重寫的equals()和hashcode()方法也一樣適合於這個原則。固然沒有通過重寫的類,在繼承了object類的equals()和hashcode()方法後,也會遵照這個原則。
Hashset是繼承Set接口,Set接口又實現Collection接口,這是層次關係。那麼Hashset、Hashmap、Hashtable中的存儲操做是根據什麼原理來存取對象的呢?
下面以HashSet爲例進行分析,咱們都知道:在hashset中不容許出現重複對象,元素的位置也是不肯定的。在hashset中又是怎樣斷定元素是否重複的呢?在java的集合中,判斷兩個對象是否相等的規則是:
1.判斷兩個對象的hashCode是否相等
若是不相等,認爲兩個對象也不相等,完畢
若是相等,轉入2
(這一點只是爲了提升存儲效率而要求的,其實理論上沒有也能夠,但若是沒有,實際使用時效率會大大下降,因此咱們這裏將其作爲必需的。)
2.判斷兩個對象用equals運算是否相等
若是不相等,認爲兩個對象也不相等
若是相等,認爲兩個對象相等(equals()是判斷兩個對象是否相等的關鍵)
爲何是兩條準則,難道用第一條不行嗎?不行,由於前面已經說了,hashcode()相等時,equals()方法也可能不等,因此必須用第2條準則進行限制,才能保證加入的爲非重複元素。
例1:
1 package com.bijian.study; 2 3 import java.util.HashSet; 4 import java.util.Iterator; 5 import java.util.Set; 6 7 public class HashSetTest { 8 9 public static void main(String args[]) { 10 String s1 = new String("aaa"); 11 String s2 = new String("aaa"); 12 System.out.println(s1 == s2); 13 System.out.println(s1.equals(s2)); 14 System.out.println(s1.hashCode()); 15 System.out.println(s2.hashCode()); 16 Set hashset = new HashSet(); 17 hashset.add(s1); 18 hashset.add(s2); 19 Iterator it = hashset.iterator(); 20 while (it.hasNext()) { 21 System.out.println(it.next()); 22 } 23 } 24 }
運行結果:
false true 96321 96321 aaa
這是由於String類已經重寫了equals()方法和hashcode()方法,因此hashset認爲它們是相等的對象,進行了重複添加。
例2:
1 package com.bijian.study; 2 3 import java.util.HashSet; 4 import java.util.Iterator; 5 6 public class HashSetTest { 7 8 public static void main(String[] args) { 9 HashSet hs = new HashSet(); 10 hs.add(new Student(1, "zhangsan")); 11 hs.add(new Student(2, "lisi")); 12 hs.add(new Student(3, "wangwu")); 13 hs.add(new Student(1, "zhangsan")); 14 15 Iterator it = hs.iterator(); 16 while (it.hasNext()) { 17 System.out.println(it.next()); 18 } 19 } 20 } 21 22 class Student { 23 int num; 24 String name; 25 26 Student(int num, String name) { 27 this.num = num; 28 this.name = name; 29 } 30 31 public String toString() { 32 return num + ":" + name; 33 } 34 }
運行結果:
1:zhangsan 3:wangwu 2:lisi 1:zhangsan
爲何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; } }
運行結果:
1:zhangsan 3:wangwu 2:lisi
能夠看到重複元素的問題已經消除,根據重寫的方法,即使兩次調用了new Student(1,"zhangsan"),咱們在得到對象的哈希碼時,根據重寫的方法hashcode(),得到的哈希碼確定是同樣的,固然根據equals()方法咱們也可判斷是相同的,因此在向hashset集合中添加時把它們看成重複元素看待了。
重寫equals()和hashcode()小結:
1.重點是equals,重寫hashCode只是技術要求(爲了提升效率)
2.爲何要重寫equals呢?由於在java的集合框架中,是經過equals來判斷兩個對象是否相等的
3.在hibernate中,常用set集合來保存相關對象,而set集合是不容許重複的。在向HashSet集合中添加元素時,其實只要重寫equals()這一條也能夠。但當hashset中元素比較多時,或者是重寫的equals()方法比較複雜時,咱們只用equals()方法進行比較判斷,效率也會很是低,因此引入了hashCode()這個方法,只是爲了提升效率,且這是很是有必要的。好比能夠這樣寫:
public int hashCode(){ return 1; //等價於hashcode無效 }
這樣作的效果就是在比較哈希碼的時候不能進行判斷,由於每一個對象返回的哈希碼都是1,每次都必需要通過比較equals()方法後才能進行判斷是否重複,這固然會引發效率的大大下降。
文章參考:
在Java中正確地使用equals()和hashCode()方法