今天用FindBugs查看代碼質量的時候看到以下的提示html
@Data註解包含了getter settter equals hashCode方法java
上面的英文是:重寫equals方法可能會致使equals方法失去它的一致性原則,這個問題會出現a.equals(b)==true,b.equals(a)==false的狀況。框架
補充:eclipse
重寫equals方法的要點:ide
1 自反性:對於任意的引用值x,x.equals(x)必定爲true
2 對稱性:對於任意的引用值x 和 y,當x.equals(y)返回true,y.equals(x)也必定返回true
3 傳遞性:對於任意的引用值x、y和z,若是x.equals(y)返回true,而且y.equals(z)也返回true,那麼x.equals(z)也必定返 回 true
4 一致性:對於任意的引用值x 和 y,若是用於equals比較的對象信息沒有被修改, 屢次調用x.equals(y)要麼一致地返回true,要麼一致地返回false
5 非空性:對於任意的非空引用值x,x.equals(null)必定返回false
請注意:重寫equals方法後最好重寫hashCode方法,不然兩個等價對象可能獲得不一樣的hashCode,這在集合框架中使用可能產生嚴重後果測試
下面關於使用Lombok的可能踩坑詳細描述是轉載的http://www.cnblogs.com/wuyuegb2312/p/9750462.htmlthis
雖然接觸到lombok已經有很長時間,可是大量使用lombok以減小代碼編寫仍是在新團隊編寫新代碼維護老代碼中遇到的。idea
這些是最初我不喜歡lombok的緣由。spa
做爲IDE插件+jar包,須要對IDE進行一系列的配置。目前在idea中配置還算簡單,幾年前在eclipse下也配置過,會複雜很多。.net
通常來講,對外打的jar包最好儘量地減小三方包依賴,這樣能夠加快編譯速度,也能減小版本衝突。一旦在resource包裏用了lombok,別人想看源碼也不得不裝插件。
而這種不在對外jar包中使用lombok僅僅是約定俗成,當某一天lombok第一次被引入這個jar包時,新的感染者沒法避免。
定位方法調用時,對於自動生成的代碼,getter/setter還好說,找到成員變量後find usages,再根據上下文區分是哪一種;equals()這種,想找就只能寫段測試代碼再去find usages了。
目前主流ide基本都支持自動生成getter/setter代碼,和lombok註解相比不過一次鍵入仍是一次快捷鍵的區別,實際減輕的工做量十分微小。
當這個註解設置callSuper=true
時,會調用父類的equlas()方法,對應編譯後class文件代碼片斷以下:
public boolean equals(Object o) { if (o == this) { return true; } else if (!(o instanceof BaseVO)) { return false; } else { BaseVO other = (BaseVO)o; if (!other.canEqual(this)) { return false; } else if (!super.equals(o)) { return false; } else { // 各項屬性比較 } } }
若是一個類的父類是Object(java中默認沒有繼承關係的類父類都是Object),那麼這裏會調用Object的equals()方法,以下
public boolean equals(Object obj) { return (this == obj); }
對於父類是Object且使用了@EqualsAndHashCode(callSuper = true)
註解的類,這個類由lombok生成的equals()方法只有在兩個對象是同一個對象時,纔會返回true,不然總爲false,不管它們的屬性是否相同。這個行爲在大部分時間是不符合預期的,equals()失去了其意義。即便咱們指望equals()是這樣工做的,那麼其他的屬性比較代碼即是累贅,會大幅度下降代碼的分支覆蓋率。以一個近6000行代碼的業務系統舉例,是否修復該問題並編寫對應測試用例,可使總體的jacoco分支覆蓋率提升10%~15%。
相反地,因爲這個註解在jacoco下只算一行代碼,未覆蓋行數倒不會太多。
有幾種解決方法能夠參考:
callSuper = true
。若是父類是Object,推薦使用。@data
註解包含@EqualsAndHashCode
註解,因爲不調用父類equals(),避免了Object.equals()的坑,但可能帶來另外一個坑。詳見@data章節
。
上文提到@EqualsAndHashCode(callSuper = true) 註解的坑,那麼 @data
是否能夠避免呢?很不幸的是,這裏也有個坑。
因爲 @data
實際上就是用的 @EqualsAndHashCode
,沒有調用父類的equals(),當咱們須要比較父類屬性時,是沒法比較的。示例以下:
@Data public class ABO { private int a; } @Data public class BBO extends ABO { private int b; public static void main(String[] args) { BBO bbo1 = new BBO(); BBO bbo2 = new BBO(); bbo1.setA(1); bbo2.setA(2); bbo1.setB(1); bbo2.setB(1); System.out.print(bbo1.equals(bbo2)); // true } }
很顯然,兩個子類忽略了父類屬性比較。這並非由於父類的屬性對於子類是不可見——即便把父類private屬性改爲protected,結果也是同樣——而是由於lombok自動生成的equals()只比較子類特有的屬性。
@data
就不要有繼承關係,相似kotlin的作法,具體探討見下一節@EqualsAndHashCode(callSuper = true)
。lombok會以顯式指定的爲準。在瞭解了 @data
的行爲後,會發現它和kotlin語言中的data修飾符有點像:都會自動生成一些方法,而且在繼承上也有問題——前者一旦有繼承關係就會踩坑,然後者修飾的類是final的,不容許繼承。kotlin爲何要這樣作,兩者有沒有什麼聯繫呢?在一篇流傳較廣的文章(拋棄 Java 改用 Kotlin 的六個月後,我後悔了(譯文))中,對於data修飾符,提到:
Kotlin 對 equals()、hashCode()、toString() 以及 copy() 有很好的實現。在實現簡單的DTO 時它很是有用。但請記住,數據類帶有嚴重的侷限性。你沒法擴展數據類或者將其抽象化,因此你可能不會在覈心模型中使用它們。
這個限制不是 Kotlin 的錯。在 equals() 沒有違反 Liskov 原則的狀況下,沒有辦法產生正確的基於值的數據。
對於Liskov(里氏替換)原則,能夠簡單歸納爲:
一個對象在其出現的任何地方,均可以用子類實例作替換,而且不會致使程序的錯誤。換句話說,當子類能夠在任意地方替換基類且軟件功能不受影響時,這種繼承關係的建模纔是合理的。
根據上一章的討論,equals()的實現其實是受業務場景影響的,不管是否使用父類的屬性作比較都是有可能的。可是kotlin沒法決定equals()默認的行爲,不使用父類屬性就會違反了這個原則,使用父類屬性有可能落入調用Object.equals()的陷阱,進入了兩難的境地。
kotlin的開發者迴避了這個問題,不使用父類屬性而且禁止繼承便可。只是kotlin的使用者就會發現本身定義的data對象無法繼承,不得不刪掉這個關鍵字手寫其對應的方法。
回過頭來再看 @data
,它並無避免這些坑,只是把更多的選擇權交給開發者決定,是另外一種作法。