最近做項目,在一次寫equals方法時突然悟出了一些心得,小記之,以備後用。在《Effective Java(第二版)》的Item7中提出我們要盡量避免重新equals方法,他同時也列舉了幾種我們不需要實現equals方法的情況:
1)類的每個實例從本質上來說是唯一的,如Thread類的實例。
2)我們並不會用到該類的equals方法,如Random類,雖然可以比較兩個Random的實例,以判斷兩個實例是否可以產生相同的隨機數,設計者認為這樣的需求用到的場合很少,因而就沒有重寫equals方法。
3)父類已經實現了equals方法,並且父類實現方式和子類實現方式是一樣的,如大部分的Set實現的equals方法使用AbstractSet類提供的equals方法,List實現則使用AbstractList,Map實現使用AbstractMap的。
4)一個private類或package-private類,我們自己可以確保我們不會使用到它們的equals方法。
同時書也提出一般只有值類型的類才需要實現equals方法,像Date、Integer、Order(作為bean來使用)等。
另外,我們在實現equals方法是也要遵循以下幾個原則:
1)自反性(reflexive):x.equals(x)==true
2)對稱性(symmetric):x.equals(y)==y.equals(x)
3)傳遞性(transitive):若x.equals(y)==true, y.equals(z)==true,則x.equals(z)==true。
4)一致性(consistent):多次調用x.equals(y)的結果應該是一樣的。
5)對任何非null實例x,x.equals(null)==false。
根據這些特性,我們可以寫出如下代碼:
1 public class Customer implements Serializable {
2 private static final long serialVersionUID = 1L;
3
4 private String id;
5 private String name;
6 private String role;
7
8 @Override
9 public boolean equals(Object obj) {
10 if(obj == null) {
11 return false;
12 }
13
14 if(this == obj) {
15 return true;
16 }
17
18 if(!(obj instanceof Customer)) {
19 return false;
20 }
21
22 Customer other = (Customer)obj;
23 return (ObjectUtils.equals(id, other.id) &&
24 ObjectUtils.equals(name, other.name) &&
25 ObjectUtils.equals(role, other.role));
26 }
27
28 public String getId() {
29 return id;
30 }
31 public void setId(String id) {
32 this.id = id;
33 }
34 public String getName() {
35 return name;
36 }
37 public void setName(String name) {
38 this.name = name;
39 }
40 public String getRole() {
41 return role;
42 }
43 public void setRole(String role) {
44 this.role = role;
45 }
46 }其中ObjectUtils類的代碼如下:
1 public class ObjectUtils {
2
3 /**
4 * Compare whether the left and right is equals
5 * It has already considered the null case
6 *
7 * @param left
8 * @param right
9 * @return
10 */
11 public static boolean equals(Object left, Object right) {
12 if(left == null && right == null) {
13 return true;
14 }
15 if(left == null && right != null) {
16 return false;
17 }
18 return left.equals(right);
19 }
20 }在《Effective Java》這本書中,貌似equals實現方法前面沒有null、this的判斷,因為instanceof可以解決null的問題,而super.equals()方法可以解決this問題,但是我還是喜歡把它們都分出來,這樣寫的更加明了一些。另外,事實上,這裡的實現並沒有遵循對稱性的原則,因為如果A是B的子類,而這個equals方法在A類中,那麼AInstance.equals(BInstance)==false,若B也實現了類似的equals方法,則BInstance.equals(AInstance)==true(當A沒有新的比較字段時,或許這個時候A根本就不需要實現equals方法,如本文開頭列出的第三條),這是因為AInstance instanceof BInstance == true,反之則為false。不過由於這種情況並不常見,所以就不去care了。:)
事實上,這裡我之所以要記錄這些代碼,主要是因為有ObjectUtils類的存在。記得以前在學C#的時候,它的Object類提供了一個靜態的Equals方法,我一直對這個方法的存在感到很疑問,直到這次自己寫這個equals方法才弄明白,因為雖然在equals方法實現中,最後還要判斷類字段是否equals,然後這些字段都有可能是null的,如果沒有提供這個靜態的equals方法,我們就需要自己來判斷每個字段是否為null,然後才可以調用它的equals方法,這樣就比較麻煩了,而Object.Equals方法正是對這種行為的封裝,我們只要使用一個方法就可以安全的實現類成員的equals。這也是我加ObjectUtils類的意義所在。希望以後能有機會向這個ObjectUtils類填充更多的實用方法。:)
PS:如一樓所說在commons中的EqualsBuilder已經實現了相同的功能,而且代碼更加完善,有興趣的可以看看那裡的代碼,我這裡只是對這次新的的記錄,代碼只是對當前我考慮的場景中使用,並沒有考慮其他方面。另外,在《Effective Java》中也是建議equals和hashCode兩個方法應該是同時實現的,一樓也有說可以用HashCodeBuider來實現,這個大家也不妨可以去看看裡面的源碼,最近時間不多,以後回來再看。。。。。。