建議47: 在equals中使用getClass進行類型判斷
本節我們繼續討論覆寫equals的問題。這次我們編寫一個員工Employee類繼承Person類,這很正常,員工也是人嘛,而且在JEE中JavaBean有繼承關系也很常見,代碼如下:
1 public class Client { 2 public static void main(String[] args) { 3 Employee e1 = new Employee("張三",100); 4 Employee e2 = new Employee("張三",1001); 5 Person p1 = new Person("張三"); 6 System.out.println(p1.equals(e1)); 7 System.out.println(p1.equals(e2)); 8 System.out.println(e1.equals(e2)); 9 } 10 } 11 12 class Person{ 13 private String name; 14 15 public Person(String _name){ 16 name = _name; 17 } 18 19 @Override 20 public boolean equals(Object obj) { 21 if(obj instanceof Person){ 22 Person p = (Person) obj; 23 return name.equalsIgnoreCase(p.getName().trim()); 24 } 25 return false; 26 } 27 28 public String getName() { 29 return name; 30 } 31 32 public void setName(String name) { 33 this.name = name; 34 } 35 } 36 37 class Employee extends Person{ 38 private int id; 39 /*id的getter/setter方法省略*/ 40 public Employee(String _name,int _id) { 41 super(_name); 42 id = _id; 43 } 44 45 public int getId() { 46 return id; 47 } 48 49 public void setId(int id) { 50 this.id = id; 51 } 52 53 @Override 54 public boolean equals(Object obj) { 55 if(obj instanceof Employee){ 56 Employee e = (Employee) obj; 57 return super.equals(obj)&& e.getId() == id; 58 } 59 return false; 60 } 61 }
輸出結果:
true true false
很不給力嘛,p1竟然等於e1,也等於e2,為什么不是同一個類的兩個實例竟然也會相等呢?這很簡單,因為p1.equals(e1) 是調用父類Person的equals方法進行判斷的,它使用instanceof關鍵字檢查e1是否是Person的實例,由於兩者存在繼承關系,那結果當然是true了,相等也就沒有任何問題了,但是反過來就不成立了,e1或e2可不等於p1,這也是違反對稱性原則的一個典型案例。
更玄的是p1與e1、e2相等,但e1竟然與e2不相等,似乎一個簡單的等號傳遞都不能實現。這才是我們要分析的真正重點:e1.equals(e2)調用的是子類Employee的equals方法,不僅僅要判斷姓名相同,還要判斷工號是否相同,兩者工號是不同的,不相等也是自然的了。等式不傳遞是因為違反了equals的傳遞性原則,傳遞性原則是指對於實例對象x、y、z來說,如果x.equals(y)返回true,y.equals(z)返回true,那么x.equals(z)也應該返回true。
這種情況發生的關鍵是父類使用了instanceof關鍵字,它是用來判斷是否是一個類的實例對象的,這很容易讓子類“鑽空子”。想要解決也很簡單,使用getClass來代替instanceof進行類型判斷,Person類的equals方法修改后如下所示:
1 public boolean equals(Object obj) { 2 if(obj!=null && obj.getClass() == this.getClass()){ 3 Person p = (Person) obj; 4 if(p.getName()==null || name==null){ 5 return false; 6 }else{ 7 return name.equalsIgnoreCase(p.getName()); 8 } 9 } 10 return false; 11 }
當然,考慮到Employee也有可能被繼承,也需要把它的instanceof修改為getClass。總之,在覆寫equals時建議使用getClass進行類型判斷,而不要使用instanceof。