如何覆寫java中的equals和hashcode方法
這篇文章算是一個翻譯,原文在:點選開啟連結,但我並沒有一字不差全部翻譯,只是選出一些重點,大家有興趣可以看看原文。
Equals和 hashCode是java中一個物件的兩個基本方法和core java的重要組成部分。Equals用來比較物件的相等性,hashcode用來生成相應物件的整數形編碼。Equals和hashCode在core java中有廣泛的應用,例如在hashmap中插入和取回資料。
Equals方法覆寫遵循以下規則:
(1)自反性。一個物件必須和它自身相等;
(2) 對稱性。如果a.equals(b) 為true 那麼 b.equals(a) 也必須為true;
(3) 傳遞性。如果 a.equals(b) 為true 並且 b.equals(c) 為 true 那麼 c.equals(a) 也必須 為 true;
(4) 一致性。除非欄位的值改變,否則多次呼叫equals()方法應該返回相同的結果。
(5) Null比較。任何物件和null比較必須返回false,並且不能夠出現NullPointerException結果。
Equals 和 hashCode的關係
Equals()和hashcode()必須遵守以下規則:
(1) 如果兩個物件執行equals()方法是相等的,那麼執行hashcode()方法的結果也必須是相等的;
(2) 如果兩個物件執行equals()方法不相等,那麼執行hashcode()方法的結果可以相等頁可以不相等。
重寫equals方法的步驟
這是大多數java程式設計師重寫equals方法的標準做法:
(1) 做this檢查。如果是this則返回true;
(2) 做null檢查。如果是null則返回false;
(3) 做instanceof檢查。如果instanceof返回false,那麼equals方法就返回false。在做了一些研究之後,我發現可以用getClass()方法代替instanceof來比較型別的相等性,因為instanceof在檢查子類的時候也會返回
if((obj == null) || (obj.getClass() != this.getClass()))
return false;
(4) 物件型別轉換。
(5) 以數值型屬性開始比較每個屬性值,因為數值型屬性比較最快而且在結合檢查的時候可以使用短路操作。如果第一個屬性不匹配,那麼就可以返回false而不需要匹配剩餘的屬性。在每個屬性呼叫equals方法前也要記得做null檢查,避免在遞迴equals檢查時出現NullPointerException。
覆寫equlas方法的程式碼樣例
/**
* Person class with equals and hashcode implementation in Java
* @author Javin Paul
*/
public class Person {
private int id;
private String firstName;
private String lastName;
public int getId() { return id; }
public void setId(int id) { this.id = id;}
public String getFirstName() { return firstName; }
public void setFirstName(String firstName) { this.firstName = firstName; }
public String getLastName() { return lastName; }
public void setLastName(String lastName) { this.lastName = lastName; }
@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if (obj == null || obj.getClass() != this.getClass()) {
return false;
}
Person guest = (Person) obj;
return id == guest.id
&& (firstName == guest.firstName
|| (firstName != null && firstName.equals(guest.getFirstName())))
&& (lastName == guest.lastName
|| (lastName != null && lastName .equals(guest.getLastName())));
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result
+ ((firstName == null) ? 0 : firstName.hashCode());
result = prime * result + id;
result = prime * result
+ ((lastName == null) ? 0 : lastName.hashCode());
return result;
}
}
在覆寫equals時的常見錯誤
(1) 過載了equals方法而沒有重寫它
這是我見到的覆寫equals方法最常見的錯誤。equasl的語法是public boolean equals(Object obj),但許多人無意間過載了equals方法 public boolean equals(Person obj)。這個錯誤因為靜態繫結而非常難以察覺。
(2) 第二個錯誤是在覆寫equals方法時沒有為成員變數沒有做null檢查,最終在呼叫equals方法時導致 NullPointerException。正確的做法是:
firstname == guest.firstname || (firstname != null && firstname.equals(guest.firstname)));
(3) 只覆寫equals()方法而沒有覆寫hashCode方法。你必須同時覆寫equals和hashCode的方法,要不然這個物件就不能在HashMap中作為一個key,因為HashMap是依賴這兩個方法的。
(4) 最後一個錯誤是在覆寫equals方法的時候沒有保持equals方法和compareTo()的一致性,這不是正常的要求,只是為了服從Set的約定避免重複。SortedSet 的實現例如TreeSet使用compareTo方法來比較兩個物件,例如字串。如果compareTo方法和equals方法沒有保持一致,那麼TreeSet就會允許重複,這樣就損壞了Set不能重複的約定。想要學習更多可以檢視Things to remember whileoverriding compareTo in Java
在寫equals方法時的5個小提示
1)大多數的IDE,例如NetBeans, Eclipse 和 IntelliJ IDEA提供生成equals和hashCode方法的支援。在In Eclipse do the right click-> source-> generate hashCode() and equals().
2)如果你的類中有一些唯一的商業主鍵,那麼在equals方法中比較這些主鍵欄位就足夠了而不需要比較所有的欄位。例如“id”是每個Person唯一的,那麼只需要比較id就可以鑑別兩個Person是不是相同的。
3)在覆寫hashCode方法時,要確保你使用的所有欄位都是在equals方法裡是相同的。
4)String和包裝類例如Integer,Float和Double需要覆寫equals方法而StringBuffer不需要。
5)只要有可能,要努力通過final使你的欄位不可變。Equals方法在不可變欄位上要比可變欄位安全的多。