hashCode是編譯器爲不一樣對象產生的不一樣整數,根據equal方法的定義:若是兩個對象是相等(equal)的,那麼兩個對象調用hashCode必須產生相同的整數結果,即:equal爲true,hashCode必須爲true,equal爲false,hashCode也必須爲false,因此必須重寫hashCode來保證與equal同步。
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;
}
}
在java的集合中,判斷兩個對象是否相等的規則是:
1,判斷兩個對象的hashCode是否相等
若是不相等,認爲兩個對象也不相等,完畢
若是相等,轉入2
2,判斷兩個對象用equals運算是否相等
若是不相等,認爲兩個對象也不相等
若是相等,認爲兩個對象相等
=====================================
一、 爲何要重載equal方法?
答案:由於Object的equal方法默認是兩個對象的引用的比較,意思就是指向同一內存,地址則相等,不然不相等;若是你如今須要利用對象裏面的值來判斷是否相等,則重載equal方法。
二、 爲何重載hashCode方法?
答案:通常的地方不須要重載hashCode,只有當類須要放在HashTable、HashMap、HashSet等等hash結構的集合時纔會重載hashCode,那麼爲何要重載hashCode呢?就HashMap來講,比如HashMap就是一個大內存塊,裏面有不少小內存塊,小內存塊裏面是一系列的對象,能夠利用hashCode來查找小內存塊hashCode%size(小內存塊數量),因此當equal相等時,hashCode必須相等,並且若是是object對象,必須重載hashCode和equal方法。
三、 爲何equals()相等,hashCode就必定要相等,而hashCode相等,卻不要求equals相等?
答案:一、由於是按照hashCode來訪問小內存塊,因此hashCode必須相等。
二、HashMap獲取一個對象是比較key的hashCode相等和equal爲true。
之因此hashCode相等,卻能夠equal不等,就好比ObjectA和ObjectB他們都有屬性name,那麼hashCode都以name計算,因此hashCode同樣,可是兩個對象屬於不一樣類型,因此equal爲false。
四、 爲何須要hashCode?
一、 經過hashCode能夠很快的查到小內存塊。
二、 經過hashCode比較比equal方法快,當get時先比較hashCode,若是hashCode不一樣,直接返回false。
如下是一個具體類的實例代碼:
public class Person
{
private String name;
private int age;
@Override
public int hashCode()
{
final int prime = 31;
int result = 1;
result = prime * result + age;
result = prime * result + ((name == null) ? 0 : name.hashCode());
return result;
}
@Override
public boolean equals(Object obj)
{
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Person other = (Person) obj;
if (age != other.age)
return false;
if (name == null)
{
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
return true;
}
}
=====================================
1. 首先equals()和hashcode()這兩個方法都是從object類中繼承過來的。
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()方法時,必須遵照的準則,若是違反會出現意想不到的結果,請你們必定要遵照。
2. 其次是hashcode() 方法,在object類中定義以下:
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方法的次數就大大下降了,幾乎只須要一兩次。
因此,Java對於eqauls方法和hashCode方法是這樣規定的:
一、若是兩個對象相同,那麼它們的hashCode值必定要相同;二、若是兩個對象的hashCode相同,它們並不必定相同 上面說的對象相同指的是用eqauls方法比較。
你固然能夠不按要求去作了,但你會發現,相同的對象能夠出如今Set集合中。同時,增長新元素的效率會大大降低。
3.這裏咱們首先要明白一個問題:
equals()相等的兩個對象,hashcode()必定相等;
equals()不相等的兩個對象,卻並不能證實他們的hashcode()不相等。換句話說,equals()方法不相等的兩個對象,hashcode()有可能相等。(個人理解是因爲哈希碼在生成的時候產生衝突形成的)。
反 過來:hashcode()不等,必定能推出equals()也不等;hashcode()相等,equals()可能相等,也可能不等。解釋下第3點的 使用範圍,個人理解是在object、String等類中都能使用。在object類中,hashcode()方法是本地方法,返回的是對象的地址值,而 object類中的equals()方法比較的也是兩個對象的地址值,若是equals()相等,說明兩個對象地址值也相等,固然hashcode()也 就相等了;在String類中,equals()返回的是兩個對象內容的比較,當兩個對象內容相等時,
Hashcode()方法根據 String類的重寫(第2點裏面已經分析了)代碼的分析,也可知道hashcode()返回結果也會相等。以此類推,能夠知道Integer、 Double等封裝類中通過重寫的equals()和hashcode()方法也一樣適合於這個原則。固然沒有通過重寫的類,在繼承了object類的 equals()和hashcode()方法後,也會遵照這個原則。
4.談到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("zhaoxudong");
String s2=new String("zhaoxudong");
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);
/*實質上在添加s1,s2時,運用上面說到的兩點準則,能夠知道hashset認爲s1和s2是相等的,是在添加劇復元素,因此讓s2覆蓋了s1;*/
Iterator it=hashset.iterator();
while(it.hasNext())
{
System.out.println(it.next());
}
最後在while循環的時候只打印出了一個」zhaoxudong」。
輸出結果爲:false
true
-967303459
-967303459
這是由於String類已經重寫了equals()方法和hashcode()方法,因此在根據上面的第1.2條原則斷定時,hashset認爲它們是相等的對象,進行了重複添加。
可是看下面的程序:
import java.util.*;
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
能夠看到重複元素的問題已經消除。
關於在hibernate的pojo類中,從新equals()和hashcode()的問題:
1),重點是equals,重寫hashCode只是技術要求(爲了提升效率)
2),爲何要重寫equals呢,由於在java的集合框架中,是經過equals來判斷兩個對象是否相等的
3),在hibernate中,常用set集合來保存相關對象,而set集合是不容許重複的。咱們再來談談前面提到在向hashset集合中添加元素時,怎樣判斷對象是否相同的準則,前面說了兩條,其實只要重寫equals()這一條也能夠。
但 當hashset中元素比較多時,或者是重寫的equals()方法比較複雜時,咱們只用equals()方法進行比較判斷,效率也會很是低,因此引入了 hashcode()這個方法,只是爲了提升效率,可是我以爲這是很是有必要的(因此咱們在前面以兩條準則來進行hashset的元素是否重複的判斷)。
好比能夠這樣寫:
public int hashCode(){
return 1;}//等價於hashcode無效
這樣作的效果就是在比較哈希碼的時候不能進行判斷,由於每一個對象返回的哈希碼都是1,每次都必需要通過比較equals()方法後才能進行判斷是否重複,這固然會引發效率的大大下降。
我有一個問題,若是像前面提到的在hashset中判斷元素是否重複的必要方法是equals()方法(根據網上找到的觀點),可是這裏並無涉及到關於哈希表的問題,但是這個集合卻叫hashset,這是爲何??
我 想,在hashmap,hashtable中的存儲操做,依然遵照上面的準則。java