從Scala看canEqual與正確的的equals實現(xiàn)
Equals實現(xiàn)在Java中有著很多的問題(詳見《所有的Equals方法實現(xiàn)都是錯誤的》),不過這些問題并非令人完全喪氣。下面通過Scala作者的一篇文章中探討equals實現(xiàn)以及canEqual的使用。
在 Effective Java 中,Joshua Bloch 提到,如果一個可實例化的類定義了 equals 方法。另有一個子類繼承它,也定義了額外一些屬性,并且 equals 方法中需要使用這些新定義的屬性進行相等性判斷。那么就不可能保證 equals 語義的正確。
相信看過 Effective Java 的人當年讀到這里時都會覺得喪氣。就好像完美的世界突然有了一個無法縫合的裂口。先不要完全喪失興趣,看看下面的文章:
How to Write an Equality Method in Java (51CTO曾翻譯此文為《所有的Equals方法實現(xiàn)都是錯誤的》)
這篇主要由 Scala 的作者 Martin Odersky 執(zhí)筆的文章中提到了一個有意思的方法。每個類在定義 equals 時,首先先判斷 canEqual 能不能校驗通過。canEqual 的作用就是限定:只有當被比較的對象是當前對象的子類或同類時才能通過。
- class Point {
- // 屬性定義
- ...
- boolean canEqual(Object other) {
- return (other instanceof Point);
- }
- @Override boolean equals(Object other) {
- if (other instanceof Point) {
- Point that = (Point) other;
- if (that.canEqual(this) && ...) return true
- }
- return false;
- }
- }
子類的定義與父類相似。
也就是說,在這樣的約定下,如果拿一個父類實例和子類實例用 equals 比較肯定會返回 false。關于這篇文章,有興趣的話可以看看相應的討論。
討論主要集中在文章里的方法是否違背了 Liskov Substitution Principle (LSP),以及如果違背了那么這個問題有多嚴重上??催^下面的分析后大家也許會覺得這種討論沒有太多意義。
我個人推薦這種 canEqual 方法。我說“方法”而不說“解決方案”是因為我覺得 Odersky 所描述的 equals 實現(xiàn)與 Bloch 本來所期望的 equals 邏輯模型并不一致。想像 Odersky 文章中的例子。有一個類 - 點(Point),及其子類 - 有色點(ColoredPoint)。如果一個有色點實例,其坐標與一個普遍點坐標一樣,又因為有色點“是”點,所以這兩點應該“相等”。大家都期望這樣一個結論是成立的,所以當看到 Bloch 的結論時會覺得面向對象有其固有的自相矛盾之處。但是這樣一個結論卻并不是天然成立的。一個沒有顏色的點與一個有顏色的點能相等嗎?有人會說,如果 ColoredPoint 里面的 color 屬性是一個枚舉,而且那個子類被實例化成 Color.UNSPECIFIED(未指定的顏色),那么這兩個點邏輯上就應該相等了吧。我認為,如果 ColoredPoint.color 可以有這樣一個屬性值的話,那么 Point 類就應該被定義為抽象類。Point 類此時實例化沒有意義。換句話說,如果 Point 類可以實例化,且其子類 ColoredPoint 也可以有一個“未指定的顏色”,而且兩者都定義了 equals,那么出現(xiàn)這種情況我認為是設計失敗。
再看看 LSP。LSP 說,任何可以使用父類實例的地方都可以使用子類實例代替。這里并不違反 LSP,因為如果一個地方可以這樣調(diào)用:
- Point p = new Point();
- if (p.equals(...)) {
- ...
- }
那么使用子類一樣可以調(diào)用 equals。只不過,equals 在傳入相同的參數(shù)時返回的結果可能會不一樣。但是 LSP 并不約束必須返回一樣的結果。而這正是多態(tài)的特征。
回到 Bloch 的論點上?,F(xiàn)在贊同我的人可能會覺得 Bloch 的論點有問題。其實他說得很嚴謹,沒有一絲問題。他的論點的前提是:可實例化的父類。也就是說無法針對非抽象類寫出滿足大家傳統(tǒng)期望的子類。只不過,另人失望地,他在提出這個結論后沒有給出對應的方法。相對來說,Odersky 理清了 Bloch 的邏輯模型。所以,在 Odersky 所發(fā)明的 Scala 中,canEqual 這個方法也被作為官方推薦的 equals實現(xiàn)方法。
【編輯推薦】