讀古今文學網 > 編寫高質量代碼:改善Java程序的151個建議 > 建議47:在equals中使用getClass進行類型判斷 >

建議47:在equals中使用getClass進行類型判斷

本節我們繼續討論覆寫equals的問題。這次我們編寫一個員工Employee類繼承Person類,這很正常,員工也是人嘛,而且在JEE中JavaBean有繼承關係也很常見,代碼如下:


class Employee extends Person{

private int id;

/*id的getter/setter方法省略*/

public Employee(String_name, int_id){

super(_name);

id=_id;

}

@Override

public boolean equals(Object obj){

if(obj instanceof Employee){

Employee e=(Employee)obj;

return super.equals(obj)&&e.getId()==id;

}

return false;

}

}


員工類增加了工號ID屬性,同時也覆寫了equals方法,只有在姓名和ID號都相同的情況下才表示是同一個員工,這是為了避免在一個公司中出現同名同姓員工的情況。看看上面的代碼,這裡校驗條件已經相當完備了,應該不會再出錯了,那我們編寫一個main方法來看看,代碼如下:


public static void main(Stringargs){

Employee e1=new Employee("張三",100);

Employee e2=new Employee("張三",1001);

Person p1=new Person("張三");

System.out.println(p1.equals(e1));

System.out.println(p1.equals(e2));

System.out.println(e1.equals(e2));

}


上面定義了2個員工和1個社會閒雜人員,雖然他們同名同姓,但肯定不是同一個,輸出應該都是false,那我們看看運行結果:


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方法修改後如下所示:


public boolean equals(Object obj){

if(obj!=null&&obj.getClass()==this.getClass()){

Person p=(Person)obj;

if(p.getName()==null||name==null){

return false;

}else{

return name.equalsIgnoreCase(p.getName());

}

}

return false;

}


當然,考慮到Employee也有可能被繼承,也需要把它的instanceof修改為getClass。總之,在覆寫equals時建議使用getClass進行類型判斷,而不要使用instanceof。