Object類中的.equals()方法和.hashCode()方法詳解
一、判斷相等
衆所周知,在java中判斷兩個變量或者對象是否相等,可以使用 == 操作符或者‘.equals()’方法。而.equals()方法又涉及到.hashCode()方法,他們究竟的原理是什麼,讓我們細細道來。
基本類型的相等判斷
當判斷基本類型的相等與否時,用‘==‘判斷完全沒問題,因爲基本類型的相等與否完全取決於它的值,在《java編程思想》一書中寫道:“關係操作符生成的是一個boolean結果,它們計算的是操作數的值之間的關係”。
這看似簡單的一句話其實告訴我們,用**==**這種基本操作去判斷相等是一種基本操作,並沒有“源碼”,比如如下的代碼:
import static java.lang.System.out;
public class Main {
/**
* @param args
*/
public static void main(String[] args) {
int n=3;
int m=3;
out.println(n==m);
String str = new String("hello");
String str1 = new String("hello");
String str2 = new String("hello");
out.println(str1==str2);
str1 = str;
str2 = str;
out.println(str1==str2);
}
}
會輸出 true,false,true
結果很簡單,最後一次判斷讓str1和str2指向了同一對象,所以他們就相等了。
總結:== 操作符對於基本類型來說對比的是他們的值,而對於引用類型,對比的是他們的地址。
對象類型的相等判斷
對於對象類型的相等判斷,相信大家都知道用.equals()方法。
Object類的.equals()方法長下面這個樣子:
public boolean equals(Object obj) {
return (this == obj);
}
所以很顯然,我們如果不重寫Object類的.equals()方法,那麼得到的結果和直接用 == 沒什麼區別!
二、重寫.equals()方法
我們先來看一個成功的範例:
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String anotherString = (String)anObject;
int n = value.length;
if (n == anotherString.value.length) {
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
while (n-- != 0) {
if (v1[i] != v2[i])
return false;
i++;
}
return true;
}
}
return false;
}
以上是final的String類中重寫的.equals()方法,它相當於先調用了super.equals()方法也就是直接用==判斷,之後再確認了傳進來的對象是字符串後,將其強制轉換爲字符串類型,再分割爲char數組,進行逐位判斷它的ASCII碼是否相等。
我們自己定義類的.equals()方法寫法:
1 @Override
2 public boolean equals(Object obj) {
3 if (super.equals(obj)) {
4 return true;
5 }
6 if (obj == null) {
7 return false;
8 }
9 if (getClass() != obj.getClass()) {
10 return false;
11 }
12 /*
13 * Model1 mol = (Model1)obj;
14 return (mol.code == code);
15 */
16 return (obj.hashCode() == hashCode());
17 }
解讀:
1.@override 標籤是非常建議加上的,方法重寫的原則是方法名,返回值,以及參數個數和參數類型要完全和父類方法一致,否則你寫的方法將不會覆蓋掉父類的任何方法。然而如果你犯了這些錯誤,在你寫了@override 標籤後編譯器將會報錯。
2.第3-5行調用了Object類的.equals()方法,當然你也可以寫成上面那樣。
3.第6-11行,根據java語言規範要求,.equals()方法需要具有以下的特性:
1.自反性:對於任何非空引用x,x.equals(x)應當返回true。
2.對稱性:對於任何引用x和y,當且僅當x.equals(y)返回true,y.equals(x)也應當返回true。
3.傳遞性:對於任何引用x,y和z,如果x.equals(y)返回true,y.equals(z)返回true,x.equals(z)也應該返回true。
4.一致性:如果x和y引用的對象沒有發生變化,反覆調用x.equals(y)應該返回同樣的結果。
5.對於任意非空引用x,x.equals(null)應該返回false。
所以在涉及繼承關係的兩個類去進行equals判斷時,第9行的
getClass() != obj.getClass()
不能換成
obj instanceof 'ClassName'
因爲其不滿足對稱性。
4.在第13行,意味着你已經確認了obj對象就是我們這個類的類型,所以可以通過強制類型轉換將其轉換回來,使用==比較基本類型域,使用.equals()方法比較對象域。
注:在實現邏輯時,如果該類中有多個域將涉及到相等判斷時,可以這麼寫:
return field1 == other.field1
&& field2.equals(other.field2)
&& field3.equals(other.field3)
...
不過更好的方法Objects.equals()方法是null安全的,寫成如下:
return field1 == other.field1
&& Objects.equals(field2,other.field2)
&& Objects.equals(field3,other.field3)
...
5.這裏的第16行是第13到15行的另一種寫法,因爲java開發規範還規定了:Equals與HashCode的定義必須一致,如果x.equals(y)返回true,那麼x.hashCode()就必須與y.hashCode()具有相同的值。
三、hashCode與.hashCode()方法
先來介紹一下hashcode是個什麼東東?
首先,每個對象都有hashcode,Object類中的hashCode()方法得到的就是這個對象的hashCode,同時也是這個對象的存儲地址。
當我們重寫了.hashCode()方法時,並不意味着我們改變了他們的存儲地址,只是改變了返回的值(int)
String類的hashCode()
public int hashCode() {
int h = hash;
if (h == 0 && value.length > 0) {
char val[] = value;
for (int i = 0; i < value.length; i++) {
h = 31 * h + val[i];
}
hash = h;
}
return h;
}
以上是String類重寫的hashCode()方法,它是根據字符串中每個字符的ASCII碼計算得到的,所以:
String s = "Ok";
String t = new String("Ok");
這兩個字符串的hashCode是相等的,79 * 31 + 107 = 2556。
重寫hashCode方法
@override
public int hashCode(){
rerturn 7 * name.hashCode()
+ 11 * new Double(salary).hashCode()
+ 13 * hireDay.hashCode();
}
更好的方法,將採用null安全的Objects.hashCode()方法,以及使用Double.hashCode()避免創建Double對象。
@override
public int hashCode(){
rerturn 7 * Objects.hashCode(name)
+ 11 * Double.hashCode(salary)
+ 13 * Objects.hashCode(hireDay);
}
更簡潔的方法:
public int hashCode(){
rerturn Objects.hash(name,salary,hireDay)
}
Objects.hash()的底層實現:
public static int hash(Object... values) {
return Arrays.hashCode(values);
}
public static int hashCode(Object a[]) {
if (a == null)
return 0;
int result = 1;
for (Object element : a)
result = 31 * result + (element == null ? 0 : element.hashCode());
return result;
}
四、參考資料
[1](美)凱S.霍斯特曼,(Cay S.Horstmann)著,周立新,陳波,葉乃文,鄺勁筠,杜永萍譯.java核心技術 卷1 基礎知識[M].機械工業出版社,2018年第10版,166-172頁.
[2] 海子的博客,https://www.cnblogs.com/dolphin0520/p/3592500.html
[3] 最好的資料就是源碼