为什么要重写hashCode()方法和equals()方法以及如何进行重写
一、前言
本篇文章主要探討的問題有三個:
1、首先我們為什么需要重寫hashCode()方法和equals()方法?
2、在什么情況下需要重寫hashCode()方法和equals()方法?
3、如何重寫這兩個方法?
二、為什么需要重寫hashCode()方法和equals()方法
首先,為什么要重寫equals()方法。我們在定義類時,我們經常會希望兩個不同對象的某些屬性值相同時就認為他們相同,所以我們要重寫equals()方法,按照原則,我們重寫了equals()方法,也要重寫hashCode()方法。
Java中的超類Object類中定義的equals()方法是用來比較兩個引用所指向的對象的內存地址是否一致,Object類中equals()方法的源碼:
public boolean equals(Object obj) {return (this == obj); }Object類中hashCode()方法的源碼:
public native int hashCode();Object類中的hashCode()方法,用的是native關鍵字修飾,說明這個方法是個原生函數,也就說這個方法的實現不是用java語言實現的,是使用c/c++實現的,并且被編譯成了DLL,由java去調用,jdk源碼中不包含。對于不同的平臺它們是不同的,java在不同的操作系統中調用不同的native方法實現對操作系統的訪問,因為java語言不能直接訪問操作系統底層,因為它沒有指針。
(1)這種方法調用的過程:
1、在java中申明native方法,然后編譯;
2、用javah產生一個 .h 文件;
3、寫一個 .cpp文件實現native導出方法,其中需要包含第二步產生的.h文件(其中又包含了jdk帶的jni.h文件);
4、將.cpp文件編譯成動態鏈接庫文件;
5、在java中用System.loadLibrary()文件加載第四步產生的動態鏈接庫文件,然后這個navite方法就可被訪問了
Java的API文檔對hashCode()方法做了詳細的說明,這也是我們重寫hashCode()方法時的原則【Object類】;
(2)重點要注意的是:
a. 在java應用程序運行時,無論何時多次調用同一個對象時的hashCode()方法,這個對象的hashCode()方法的返回值必須是相同的一個int值;
b. 如果兩個對象equals()返回值為true,則他們的hashCode()有返回相同的int值;
c. 如果兩個對象equals()返回值為false,則他們的hashCode()值也有可能相同;
現在,我們到這里可以看出,我們重寫了equals()方法也要重寫hashCode()方法,這是因為要保證上面所述的b,c原則;所以java中的很多類都重寫了這兩個方法,例如String類,包裝類等。
三、在什么情況下需要重寫hashCode()方法和equals()方法
當我們自定義的一個類,想要把它的實例保存在集合中時,我們就需要重寫這兩個方法;集合(Collection)有兩個類,一個是List,一個是Set。
List:集合中的元素是有序的,可以重復的; Set:無序,不可重復的;12(1)以HashSet來舉例:
HashSet存放元素時,根據元素的hashCode方法計算出該對象的哈希碼,快速找到要存儲的位置,然后進行比較,
比較過程如下:
- 如果該對象哈希碼與集合已存在對象的哈希碼不一致,則該對象沒有與其他對象重復,添加到集合中!
- 如果存在于該對象相同的哈希碼,那么通過equals方法判斷兩個哈希碼相同的對象是否為同一對象(判斷的標準是:屬性是否相同)
- 相同對象,不添加。
- 不同對象,添加。
注意:如果返回值為false,則這個時候會以鏈表的形式在同一個位置上存放兩個元素,這會使得HashSet的性能降低,因為不能快速定位了。示意圖如下:
還有一種情況就是兩個對象的hashCode()返回值不同,但是equals()返回true,這個時候HashSet會把這兩個對象都存進去,這就和Set集合不重復的規則相悖了;所以,我們重寫了equals()方法時,要按照b,c規則重寫hashCode()方法!
四、如何重寫這兩個方法
如果你決定要重寫equals()方法,那么你一定還要明確這么做所帶來的風險,并確保自己能寫出一個健壯的equals()方法。
一定要注意的一點是,在重寫equals()后,一定要重寫hashCode()方法。
(1)我們先看看 JavaSE 8 Specification中對equals()方法的說明:
The equals method implements an equivalence relation on non-null object references:
- It is reflexive: for any non-null reference value x, x.equals(x) should return true.
- It is symmetric: for any non-null reference values x and y, x.equals(y) should return true if and only if y.equals(x) returns true.
- It is transitive: for any non-null reference values x, y, and z, if x.equals(y) returns true and y.equals(z) returns true, then x.equals(z) should return true.
- It is consistent: for any non-null reference values x and y, multiple invocations of x.equals(y) consistently return true or consistently return false, provided no information used in equals comparisons on the objects is modified.
- For any non-null reference value x, x.equals(null) should return false.
The equals method for class Object implements the most discriminating possible equivalence relation on objects; that is, for any non-null reference values x and y, this method returns true if and only if x and y refer to the same object (x == y has the value true).
這段話用了很多離散數學中的術數.簡單說明一下:
(2)簡單步驟:
為了說明方便,我們先定義一個程序員類(Coder):
class Coder { private String name; private int age; // getters and setters }我們想要的是,如果2個程序員對象的name和age都是相同的,那么我們就認為這兩個程序員是一個人。這時候我們就要重寫其equals()方法。因為默認的equals()實際是判斷兩個引用是否指向內在中的同一個對象,相當于 == 。 重寫時要遵循以下三步:
1、判斷是否等于自身:
if(other == this){return true; }2、 使用instanceof運算符判斷 other 是否為Coder類型的對象:
if(!(other instanceof Coder)) {return false; }3、比較Coder類中你自定義的數據域,name和age,一個都不能少:
Coder o = (Coder)other; return o.name.equals(name) && o.age == age;看到這有人可能會問,第3步中有一個強制轉換,如果有人將一個Integer類的對象傳到了這個equals中,那么會不會扔ClassCastException呢?這個擔心其實是多余的.因為我們在第二步中已經進行了instanceof 的判斷,如果other是非Coder對象,甚至other是個null, 那么在這一步中都會直接返回false, 從而后面的代碼得不到執行的機會。
上面的三步也是<Effective Java>中推薦的步驟,基本可保證萬無一失。
我們在大學計算機數據結構課程中都已經學過哈希表(hash table)了,hashCode()方法就是為哈希表服務的。
當我們在使用形如HashMap, HashSet這樣前面以Hash開頭的集合類時,hashCode()就會被隱式調用以來創建哈希映射關系。
<Effective Java>中給出了一個能最大程度上避免哈希沖突的寫法,但我個人認為對于一般的應用來說沒有必要搞的這么麻煩.如果你的應用中HashSet中需要存放上萬上百萬個對象時,那你應該嚴格遵循書中給定的方法.如果是寫一個中小型的應用,那么下面的原則就已經足夠使用了:
要保證Coder對象中所有的成員都能在hashCode中得到體現.
@Override public int hashCode() { int result = 17; result = result * 31 + name.hashCode(); result = result * 31 + age; return result; }其中int result = 17你也可以改成20, 50等等都可以.看到這里我突然有些好奇,想看一下String類中的hashCode()方法是如何實現的.查文檔知:
“Returns a hash code for this string. The hash code for a String object is computed as
s[0]*31^(n-1) + s[1]*31^(n-2) + … + s[n-1]
using int arithmetic, where s[i] is the ith character of the string, n is the length of the string, and ^ indicates exponentiation. (The hash value of the empty string is zero.)”
對每個字符的ASCII碼計算n - 1次方然后再進行加和,可見Sun對hashCode的實現是很嚴謹的. 這樣能最大程度避免2個不同的String會出現相同的hashCode的情況.
參考文章:
1、http://blog.csdn.net/jing_bufferfly/article/details/50868266
2、http://blog.csdn.net/neosmith/article/details/17068365
總結
以上是生活随笔為你收集整理的为什么要重写hashCode()方法和equals()方法以及如何进行重写的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 学习Spring Boot:(二十五)使
- 下一篇: 我的世界minecraft-Python