簡體   English   中英

字段是否需要顯式最終才能擁有“適當的”不可變對象?

[英]Do fields need to be explicitly final to have a “proper” immutable object?

您經常閱讀有關不可變對象的信息,這些對象要求最終字段在Java中不可變。 實際上是這種情況,還是僅僅是沒有公開可變性而實際上沒有改變狀態?

例如,如果你有一個由構建器模式構建的不可變對象,你可以通過讓構建器在構建時分配各個字段,或讓構建器自己保存字段並最終通過將值傳遞給返回不可變對象來實現。它的(私有)構造函數。

使字段最終具有防止實現錯誤的明顯優勢(例如允許代碼保留對構建器的引用和多次“構建”對象,而實際上是在改變現有對象),但讓Builder將其數據存儲在構建的對象似乎是DRYer。

所以問題是:假設Builder沒有提前泄漏Object並且一旦構建就停止修改對象(例如通過將其對象的引用設置為null),實際上是否有任何獲得(例如改進的線程安全性)在“如果對象的字段是最終的,那么對象的不變性?

是的,您確實從final字段獲得“線程安全”。 也就是說,保證在構造期間分配給final字段的值對所有線程可見。 線程安全的另一個替代方法是聲明字段volatile ,但是每次讀取都會產生很高的開銷......並且會讓看到你的類的人感到困惑,並想知道為什么這個“不可變”類的字段被標記為“volatile”。

在技​​術上標記final的字段是最正確的,並且最清楚地傳達您的意圖。 不幸的是,它確實使構建器模式非常麻煩。 我認為應該可以創建一個注釋處理器來合成一個不可變類的構建器,就像Project Lombok對setter和getter一樣。 真正的工作是需要IDE支持,以便您可以對不存在的構建器進行編碼。

一個Object當然可以擁有可變的私有字段,並且仍然可以作為不可變對象。 滿足不變性合同的重要性在於,對象看起來與外界不可變。 具有非最終私有字段但沒有setter的對象將例如滿足此要求。

事實上,如果你的封裝是正確的,那么你實際上可以改變內部狀態並仍然成功地作為“不可變”對象運行。 一個例子可能是某種懶惰的評估或數據結構的緩存。

例如,Clojure在其惰性序列的內部實現中執行此操作,這些對象的行為就好像它們是不可變的,但實際上只在直接請求時計算和存儲未來值。 任何后續請求都會檢索存儲的值。

但是 - 我想補充一點,即實際上想要改變不可變對象內部的地方數量可能非常少見。 如有疑問,請將其作為最終決定。

我認為您只需要考慮其運行的環境,並確定使用反射來操縱對象的框架是否存在危險。

人們可以很容易地制造一個奇怪的場景,其中一個假定的不可變對象通過POST注入攻擊被破壞,因為Web綁定框架被配置為使用反射而不是bean設置器。

你絕對可以擁有一個帶有非final字段的不可變對象。

例如,請參閱java.lang.String的java 1.6實現。

評論:@erickson

像那樣:

class X { volatile int i, j; }
X y;

// thread A: 
X x = new X;
x.i = 1;
x.j = 2;
y = x;

// thread B: 
if (y != null) {
    a = y.i; 
    b = y.j;
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM