今天在用 sonar 審覈代碼, 偶然看到下面的提示:
關於這個的提示大意是:
“克隆”不應該被覆蓋, 屬壞味道, 阻斷型錯誤
約書亞•布洛赫表示,許多人在 Java 中對 clone 方法 和 Cloneable 接口存在誤解,很大程度上是因爲重寫 clone 方法的規則很棘手, 且出錯難以糾正。
Object 的 clone 方法非常棘手。它基於屬性複製,而且是“超語言”。它創建一個對象而不調用構造函數。無法保證它保留構造函數創建的不變量。多年來,在 Sun 公司內外都存在許多錯誤,這源於這樣一個事實,即如果你只是反覆調用 super.clone 直到克隆了一個對象,那麼你就擁有了一個淺層的對象副本。克隆通常與正在克隆的對象共享狀態。如果該狀態是可變的,則您沒有兩個獨立的對象。如果您修改一個,另一個也會更改。突然之間,你會得到隨機行爲。
所以, 應該使用複製構造函數或複製工廠。
clone 無論是否實現 Cloneable 接口,此規則在被覆蓋時都會引發問題。
不合規的代碼示例
public class MyClass {
// ...
public Object clone() { // Noncompliant
//...
}
}
合規解決方案
public class MyClass {
// ...
MyClass (MyClass source) {
//...
}
}
參閱
《複製構造函數與克隆》
也可以參閱
S2157 - “Cloneables”應該實現“克隆”
S1182 - 覆蓋“clone”的類應爲“Cloneable”並調用“super.clone()”
下面爲引文翻譯
Josh Bloch 談設計
與《Effective Java》作者的對話,Josh Bloch
作者 Bill Venners
首次在JavaWorld上發表,2002年1月4日
複製構造函數與克隆
Bill Venners: 在你的書中,你建議使用複製構造函數而不是實現Cloneable和編寫clone。你能詳細說明嗎?
Josh Bloch:如果你已經閱讀了我的書中關於克隆的章節,特別是如果你看得仔細的話,你就會知道我認爲克隆已經完全壞掉的東西。有一些設計缺陷,其中最大的一個是 Cloneable 接口沒有 clone方法。這意味着它根本不起作用:實現了 Cloneable 接口並不說明你可以用它做什麼。相反,它說明了內部可能做些什麼。它說如果通過super.clone 反覆調用它最終調用 Object 的 clone 方法,這個方法將返回原始的屬性副本。
但它沒有說明你可以用一個實現 Cloneable 接口的對象做什麼,這意味着你不能做多態 clone 操作。如果我有一個 Cloneable 數組,你會認爲我可以運行該數組並克隆每個元素以製作數組的深層副本,但不能。你不能強制轉換對象爲 Cloneable 接口並調用 clone 方法,因爲 Cloneable 沒有public clone 方法,Object 類也沒有。如果您嘗試強制轉換 Cloneable 並調用該 clone 方法,編譯器會說您正在嘗試在對象上調用受保護的clone方法。
事實的真相是,您不通過實施 Cloneable 和提供 clone 除複製能力之外的公共方法爲您的客戶提供任何能力。如果您提供具有不同名稱的copy操作, 怎麼也不次於去實現 Cloneable 接口。這基本上就是你用複製構造函數做的事情。複製構造方法有幾個優點,我在本書中有討論。一個很大的優點是可以使副本具有與原始副本不同的實現。例如,您可以將一個 LinkedList 複製到 ArrayList。
Object 的 clone 方法是非常棘手的。它基於屬性複製,而且是“超語言”。它創建一個對象而不調用構造函數。無法保證它保留構造函數建立的不變量。多年來,在Sun內外存在許多錯誤,這源於這樣一個事實,即如果你只是super.clone 反覆調用鏈直到你克隆了一個對象,那麼你就擁有了一個淺層的對象副本。克隆通常與正在克隆的對象共享狀態。如果該狀態是可變的,則您沒有兩個獨立的對象。如果您修改一個,另一個也會更改。突然之間,你會得到隨機行爲。
我使用的東西很少實現 Cloneable。我經常提供實現類的 clone 公共方法,僅是因爲人們期望有。我沒有抽象類實現 Cloneable,也沒有接口擴展它,因爲我不會將實現的負擔 Cloneable 放在擴展(或實現)抽象類(或接口)的所有類上。這是一個真正的負擔,幾乎沒有什麼好處。
Doug Lea 走得更遠。他告訴我 clone 除了複製數組之外他不再使用了。您應該使用 clone 複製數組,因爲這通常是最快的方法。但 Doug 的類根本就不再實施 Cloneable了。他放棄了。而且我認爲這並非不合理。
這是一個恥辱, Cloneable 接口壞掉了,但它發生了。最初的 Java API在緊迫的期限內完成,以滿足市場窗口收緊的需求。最初的 Java 團隊做了不可思議的工作,但並非所有的 API 都是完美的。 Cloneable 是一個弱點,我認爲人們應該意識到它的侷限性。