當心覆蓋equals

咱們知道Object類中全部的非final方法equals、hashcode、toString、clone、finalize都有明確的約定,由於這些方法就是設計用來被子類覆蓋的。若是不能按照約定覆蓋,那麼其餘依賴這些方法的的類就沒法正常工做,好比HashMap和HashSet。java

咱們先來討論什麼狀況下不用覆蓋equals方法:數組

  1. Object提供默認的equals方法能夠表現出正確的行爲(默認使用==判斷);
  2. 客戶端不關心邏輯上是否相等;
  3. 超類已經覆蓋了equals方法,對子類來講工做的很好;
  4. 類是私有的或是包級私有的,這樣的話客戶端是不可能調用到equals方法的。

上述四種狀況咱們能夠不用考慮覆蓋equals方法,但有些類不免仍是要覆寫,讓咱們先看看有哪些約定的內容:性能

  1. 自反性 x.equals(x)爲true
  2. 對稱性 x.equals(y)=y.equals(x)
  3. 傳遞性 x.equals(y)=true,y.equals(z)=true,則x.equals(z)=true
  4. 一致性 只要equals中用到的信息沒有修改,那麼屢次調用返回結果不變
  5. x.equals(null)返回false(注意x非空)

對咱們來講上面的每一條都是很簡單的,甚至認爲原本就該這樣。但事實上當咱們要同時知足上述五條,有時候確是一個幾乎不可能完成的任務。優化

自反性

自反性就是對於任何非null的引用值x,x.equals(x)必須返回true。這個應該很好理解,若是違反的話,最簡單的你往集合裏添加東西,而後調用contains,你會發現他會告訴你集合不包含你剛給添加的實例。.net

對稱性

對於非空引用x,y,若是x.equals(y)返回true,那麼y.equals(x)也必須返回true。簡單來講就是x等於y,那麼y就要等於x。舉個例子看看違反的狀況:若是x是一個不去分大小寫的自定義字符串類的一個實例,那麼x假設爲「hello」,y爲普通字符串」Hello「那麼x.equals(y)應該返回true(x是自定義類,equals方法中調用了equalsIgnoreCase)。可是y.equals(x)返回false。設計

如今咱們把x放到集合中,而後list.contains("Hello")會返回什麼。true or false?code

true也好false也好,你把但願都寄託在list.contains的內部實現上,內部遍歷每一個元素時,是使用x.equals(y),仍是y.equals(x),並且這還會致使另外一個問題,及時如今能夠正確運行,那麼將來的實現改變了怎麼辦,你的代碼就不受你控制了。對象

傳遞性

傳遞性就是x等於y,y等於z,那麼x也會等於z。一樣,咱們也舉個例子來講明這個問題。如今咱們有一個Point類,座標系中的一個二維點,它的equals方法應該是比較該點的座標(x,y),若是座標相同則爲同一個點。如今咱們想要表示一個有顏色的點,咱們繼承了Point類,如今考慮equals方法。繼承

首先父類的equals方法能用嗎?答案很明顯,不能。若是用父類的equals方法,那麼顏色就不會比較,紅點就會和綠點相同。因而咱們覆寫equals方法,加上顏色比較,問題結束了嗎?接口

若是咱們比較有色點和無色點會發生什麼呢?父類會根據座標判斷是否相對,而子類只會返回false,由於缺乏顏色信息,這樣會不知足對稱性的要求。咱們再一次修正這個問題。咱們在子類equals內部先判斷類型(instanceof),若是是和父類型Point比較則不考慮顏色信息,和同類型ColorPoint則考慮顏色。此次把問題解決了嗎?

咱們再來考慮一種特殊狀況,紅點(ColorPoint),綠點(ColorPoint),點(Point)。假設三個點在同一個位置上,上面的方法咱們會的到紅點等於點,點等於綠點,可是紅點不等於綠點。你是否是發現了什麼,是的,咱們又違反了傳遞性。

咱們還能夠繼續改進用getClass判斷類型,讓不一樣類型比較都會返回false,即便是子類和父類比。可是這種方式也會帶來一些麻煩,它會限制父類的覆用。

咱們改如何辦呢?事實上這是面嚮對象語言中關於等價關係的一個基本問題:咱們沒法在擴展可實例化的類的同時,既增長新的值組件,同時又保留equals約定,除非願意放棄面向對象的抽象帶來的優點。

在這種狀況下通常使用組合,讓ColorPoint持有Point是一種不錯的選擇,這種方法有點相似於getClass方法,但能夠不用考慮繼承帶來的一些其餘方面的反作用。

對於上述狀況咱們使能夠避免的,咱們能夠把父類設計成接口或者抽象類,只要父類不和子類混用就不會出現問題。

一致性

簡單來講就是相等的對象永遠相等,不相等的永遠不相等。要想知足這一點只要在equals中不要用不可靠的資源就好了。例如,java.net.URL這個類的equals方法使用了IP地址,主機名會映射到ip,也就是隨着時間的推移equals結果可能發送變化。

非空性

若是不處理null,那麼equals裏會拋出異常。

equals使用建議

  1. 先判斷傳進來的是否是對象自己,這只是一種性能上的優化;
  2. 使用instanceof檢查參數類型是否正確;
  3. 參數轉換爲正確的類型;
  4. 比較關鍵數據是否相等,相等爲true,不然false。比較關鍵數據時,若是是對象用equals,若是是基本類型用==,可是基本類型中float和double是個例外,應該使用Float.compare方法和Double.compare方法,float和double有些特殊值好比Float.NaN,-0.0f。對於數組,上述規則用到每個元素中去。還有一個最佳實踐是,先比較最可能不一致的,或者開銷最低的內容。
相關文章
相關標籤/搜索