掃二維碼與項(xiàng)目經(jīng)理溝通
我們?cè)谖⑿派?4小時(shí)期待你的聲音
解答本文疑問(wèn)/技術(shù)咨詢/運(yùn)營(yíng)咨詢/技術(shù)建議/互聯(lián)網(wǎng)交流
Equals實(shí)現(xiàn)在Java中有著很多的問(wèn)題(詳見(jiàn)《所有的Equals方法實(shí)現(xiàn)都是錯(cuò)誤的》),不過(guò)這些問(wèn)題并非令人完全喪氣。下面通過(guò)Scala作者的一篇文章中探討equals實(shí)現(xiàn)以及canEqual的使用。

創(chuàng)新互聯(lián)網(wǎng)站建設(shè)由有經(jīng)驗(yàn)的網(wǎng)站設(shè)計(jì)師、開(kāi)發(fā)人員和項(xiàng)目經(jīng)理組成的專業(yè)建站團(tuán)隊(duì),負(fù)責(zé)網(wǎng)站視覺(jué)設(shè)計(jì)、用戶體驗(yàn)優(yōu)化、交互設(shè)計(jì)和前端開(kāi)發(fā)等方面的工作,以確保網(wǎng)站外觀精美、成都網(wǎng)站制作、網(wǎng)站設(shè)計(jì)、外貿(mào)網(wǎng)站建設(shè)易于使用并且具有良好的響應(yīng)性。
在 Effective Java 中,Joshua Bloch 提到,如果一個(gè)可實(shí)例化的類定義了 equals 方法。另有一個(gè)子類繼承它,也定義了額外一些屬性,并且 equals 方法中需要使用這些新定義的屬性進(jìn)行相等性判斷。那么就不可能保證 equals 語(yǔ)義的正確。
相信看過(guò) Effective Java 的人當(dāng)年讀到這里時(shí)都會(huì)覺(jué)得喪氣。就好像完美的世界突然有了一個(gè)無(wú)法縫合的裂口。先不要完全喪失興趣,看看下面的文章:
How to Write an Equality Method in Java (曾翻譯此文為《所有的Equals方法實(shí)現(xiàn)都是錯(cuò)誤的》)
這篇主要由 Scala 的作者 Martin Odersky 執(zhí)筆的文章中提到了一個(gè)有意思的方法。每個(gè)類在定義 equals 時(shí),首先先判斷 canEqual 能不能校驗(yàn)通過(guò)。canEqual 的作用就是限定:只有當(dāng)被比較的對(duì)象是當(dāng)前對(duì)象的子類或同類時(shí)才能通過(guò)。
- 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;
- }
- }
子類的定義與父類相似。
也就是說(shuō),在這樣的約定下,如果拿一個(gè)父類實(shí)例和子類實(shí)例用 equals 比較肯定會(huì)返回 false。關(guān)于這篇文章,有興趣的話可以看看相應(yīng)的討論。
討論主要集中在文章里的方法是否違背了 Liskov Substitution Principle (LSP),以及如果違背了那么這個(gè)問(wèn)題有多嚴(yán)重上。看過(guò)下面的分析后大家也許會(huì)覺(jué)得這種討論沒(méi)有太多意義。
我個(gè)人推薦這種 canEqual 方法。我說(shuō)“方法”而不說(shuō)“解決方案”是因?yàn)槲矣X(jué)得 Odersky 所描述的 equals 實(shí)現(xiàn)與 Bloch 本來(lái)所期望的 equals 邏輯模型并不一致。想像 Odersky 文章中的例子。有一個(gè)類 - 點(diǎn)(Point),及其子類 - 有色點(diǎn)(ColoredPoint)。如果一個(gè)有色點(diǎn)實(shí)例,其坐標(biāo)與一個(gè)普遍點(diǎn)坐標(biāo)一樣,又因?yàn)橛猩c(diǎn)“是”點(diǎn),所以這兩點(diǎn)應(yīng)該“相等”。大家都期望這樣一個(gè)結(jié)論是成立的,所以當(dāng)看到 Bloch 的結(jié)論時(shí)會(huì)覺(jué)得面向?qū)ο笥衅涔逃械淖韵嗝苤?。但是這樣一個(gè)結(jié)論卻并不是天然成立的。一個(gè)沒(méi)有顏色的點(diǎn)與一個(gè)有顏色的點(diǎn)能相等嗎?有人會(huì)說(shuō),如果 ColoredPoint 里面的 color 屬性是一個(gè)枚舉,而且那個(gè)子類被實(shí)例化成 Color.UNSPECIFIED(未指定的顏色),那么這兩個(gè)點(diǎn)邏輯上就應(yīng)該相等了吧。我認(rèn)為,如果 ColoredPoint.color 可以有這樣一個(gè)屬性值的話,那么 Point 類就應(yīng)該被定義為抽象類。Point 類此時(shí)實(shí)例化沒(méi)有意義。換句話說(shuō),如果 Point 類可以實(shí)例化,且其子類 ColoredPoint 也可以有一個(gè)“未指定的顏色”,而且兩者都定義了 equals,那么出現(xiàn)這種情況我認(rèn)為是設(shè)計(jì)失敗。
再看看 LSP。LSP 說(shuō),任何可以使用父類實(shí)例的地方都可以使用子類實(shí)例代替。這里并不違反 LSP,因?yàn)槿绻粋€(gè)地方可以這樣調(diào)用:
- Point p = new Point();
- if (p.equals(...)) {
- ...
- }
那么使用子類一樣可以調(diào)用 equals。只不過(guò),equals 在傳入相同的參數(shù)時(shí)返回的結(jié)果可能會(huì)不一樣。但是 LSP 并不約束必須返回一樣的結(jié)果。而這正是多態(tài)的特征。
回到 Bloch 的論點(diǎn)上。現(xiàn)在贊同我的人可能會(huì)覺(jué)得 Bloch 的論點(diǎn)有問(wèn)題。其實(shí)他說(shuō)得很嚴(yán)謹(jǐn),沒(méi)有一絲問(wèn)題。他的論點(diǎn)的前提是:可實(shí)例化的父類。也就是說(shuō)無(wú)法針對(duì)非抽象類寫出滿足大家傳統(tǒng)期望的子類。只不過(guò),另人失望地,他在提出這個(gè)結(jié)論后沒(méi)有給出對(duì)應(yīng)的方法。相對(duì)來(lái)說(shuō),Odersky 理清了 Bloch 的邏輯模型。所以,在 Odersky 所發(fā)明的 Scala 中,canEqual 這個(gè)方法也被作為官方推薦的 equals實(shí)現(xiàn)方法。

我們?cè)谖⑿派?4小時(shí)期待你的聲音
解答本文疑問(wèn)/技術(shù)咨詢/運(yùn)營(yíng)咨詢/技術(shù)建議/互聯(lián)網(wǎng)交流