2012-09-06 70 views
40

我看,爲了使Java中的class immutable,我們應該做到以下幾點,爲什麼要在Java中聲明一個不可變類final?

  1. 不提供任何setter方法
  2. 標記所有領域的私人
  3. 使該類最終

爲什麼需要步驟3?我爲什麼要標記類final

+0

'java.math.BigInteger'類就是例子,它的值是不可變的,但它不是最終的。 –

+0

@Nandkumar如果你有一個'BigInteger',你不知道它是否是不可變的。這是一個混亂的設計。 /'java.io.File'是一個更有趣的例子。 –

+1

不要讓子類重寫方法 - 最簡單的方法是將類聲明爲final。更復雜的方法是使構造函數保持私有狀態,並使用工廠方法構造實例 - 你可以從中解釋(或者從https://docs.oracle.com/javase/tutorial/essential/concurrency/imstrat.html –

回答

75

如果您不標記類final,我可能會突然讓您的看似不可變的類實際可變。例如,考慮下面的代碼:

public class Immutable { 
    private final int value; 

    public Immutable(int value) { 
     this.value = value; 
    } 

    public int getValue() { 
     return value; 
    } 
} 

現在,假設我做到以下幾點:

public class Mutable extends Immutable { 
    private int realValue; 

    public Mutable(int value) { 
     super(value); 

     realValue = value; 
    } 

    public int getValue() { 
     return realValue; 
    } 
    public void setValue(int newValue) { 
     realValue = newValue; 
    } 

    public static void main(String[] arg){ 
     Mutable obj = new Mutable(4); 
     Immutable immObj = (Immutable)obj;    
     System.out.println(immObj.getValue()); 
     obj.setValue(8); 
     System.out.println(immObj.getValue()); 
    } 
} 

請注意,在我的Mutable子類,我已經覆蓋的getValue行爲讀一個新的,易變的字段在我的子類中聲明。因此,最初看起來不可變的你的課程確實不是不可變的。我可以通過這個Mutable對象無論哪裏Immutable對象預計,可以做非常糟糕的事情來編碼假設對象是真正不可變的。標記基類final可以防止發生這種情況。

希望這會有所幫助!

+4

如果我Mutable m = new Mutable(4); m.setValue(5); 在這裏,我玩弄可變類對象,而不是不可變類對象。所以,我仍然困惑爲什麼不可變類不是不變的 – Anand

+8

@ anand-想象一下你有一個函數需要一個不可變參數。我可以傳遞一個'Mutable'對象給這個函數,因爲'Mutable extends Immutable'。在那個函數裏面,當你認爲你的對象是不可變的時候,我可以有一個輔助線程,並在函數運行時改變它的值。我也可以給你一個函數存儲的「可變」對象,然後在外部修改它的值。換句話說,如果你的函數假設這個值是不可變的,那麼它可能很容易中斷,因爲我可以給你一個可變對象並稍後改變它。那有意義嗎? – templatetypedef

+0

@ templatetypedef-它可能聽起來很愚蠢,但我真的仍然不清楚..lets舉一個例子說我有方法void fun(Immutable i)..我通過這個方法可變對象說m ..現在我該如何改變object..Can你可以參考代碼示例來解釋它,或者如果你可以舉一些其他的例子,我也很好。 – Anand

4

這限制了其他班級擴展你的班級。

final類不能被其他類擴展。

如果一個類擴展你想作爲不可變的類,它可能會由於繼承原則而改變類的狀態。

只是澄清「它可能會改變」。子類可以覆蓋超類的行爲,如使用方法覆蓋(如templatetypedef/Ted Hop答案)

+2

這是真的,但爲什麼這裏有必要? – templatetypedef

+1

是的,它爲什麼需要? – Anand

+0

@templatetypedef:你太快了。我正在編輯我的答案和支持點。 – kosa

5

如果它不是最終的,那麼任何人都可以擴展類並做任何他們喜歡的事情,比如提供setter,隱藏你的私有變量,可變的。

+0

儘管你是對的,但如果基類字段都是私有的並且是最終的,那麼爲什麼你可以添加可變行爲的事實將會破壞事實。真正的危險是,子類可能通過覆蓋行爲將先前不可變的對象變成可變對象。 – templatetypedef

1

如果你沒有完成它我可以擴展它,使它不可變。

public class Immutable { 
    privat final int val; 
    public Immutable(int val) { 
    this.val = val; 
    } 

    public int getVal() { 
    return val; 
    } 
} 

public class FakeImmutable extends Immutable { 
    privat int val2; 
    public FakeImmutable(int val) { 
    super(val); 
    } 

    public int getVal() { 
    return val2; 
    } 

    public void setVal(int val2) { 
    this.val2 = val2; 
    } 
} 

現在,我可以將FakeImmutable傳遞給期望不可變的任何類,並且它不會像預期的契約那樣工作。

+2

我認爲這與我的回答非常相似。 – templatetypedef

+0

是的,通過發帖檢查一半,沒有新的答案。然後發佈時,在我之前的一分鐘就有了你。至少我們沒有對所有東西都使用相同的名字。 –

+0

One Correction:在FakeImmutable類中,構造函數的名字應該是FakeImmutable不可變 –

0

假設下面的類並不final

public class Foo { 
    private int mThing; 
    public Foo(int thing) { 
     mThing = thing; 
    } 
    public int doSomething() { /* doesn't change mThing */ } 
} 

這顯然是不可變的,因爲即使子類不能修改mThing。然而,子類可以是可變的:

public class Bar extends Foo { 
    private int mValue; 
    public Bar(int thing, int value) { 
     super(thing); 
     mValue = value; 
    } 
    public int getValue() { return mValue; } 
    public void setValue(int value) { mValue = value; } 
} 

現在,一個對象,它是分配給Foo類型的變量不再保證是mmutable。這可能會導致哈希,平等,併發等問題。

19

與許多人認爲相反,使不可變類final不是必需的。

制定不可變類final的標準參數是如果你不這樣做,那麼子類可以添加可變性,從而違反超類的契約。班上的客戶將承擔不變性,但當他們下面發生變化時會感到驚訝。

如果你把這個參數其邏輯的極端,那麼所有方法應作出final,否則子類可以覆蓋在不符合其超合同的方式方法。有趣的是,大多數Java程序員都認爲這很荒謬,但對於不可變類應該是final這個想法有點不錯。我懷疑這與Java程序員通常不會完全適應不變性的概念有關,也可能與Java中final關鍵字的多重含義有關。

符合你的超類的合同是不是可以或應該總是由編譯器執行。編譯器可以強制執行合同的某些方面(例如:最少的一組方法及其類型簽名),但是編譯器無法強制實施典型合同的許多部分。

不變性是一個類的合同的一部分。這是從一些東西的人更習慣有點不同,因爲它說什麼類(及所有子類)不能做的,而我認爲大多數Java(一般OOP)的程序員往往會去想合同的涉及什麼樣的可以做什麼,而不是不能做什麼。

不變性也影響不僅僅是一個單一的方法更—它會影響整個實例—但這並不比在Java中的工作方式equalshashCode真的太大的不同。這兩種方法在Object中列出了具體的合同。這份合同非常仔細地列出了這些方法不能做的事情。這個合同在子類中更具體。以違反合同的方式覆蓋equalshashCode是非常容易的。事實上,如果你只是忽略了這兩種方法中的一種,那麼很可能是你違反了合同。所以equalshashCode已被宣佈finalObject以避免這種情況?我認爲大多數人會認爲他們不應該這樣做。同樣,沒有必要製作不可變類final

這就是說,你的大部分類,不可變或不可能,可能應該final。見有效的Java第二版項目17:「繼承或禁止它的設計和文檔」。

因此,第3步的正確版本應該是:「讓類最終完成,或者在爲子類設計時明確記錄所有子類必須繼續爲不可變的。」

+1

值得注意的是,在給定兩個對象「X」和「Y」的情況下,Java「預期」但不強制執行,「X.equals(Y)」的值將是不可變的(只要'X'和' Y'繼續引用相同的對象)。它對散列碼有類似的期望。很顯然,沒有人應該期望編譯器強化等價關係和散列碼的不變性(因爲它顯然不能)。我認爲沒有理由人們期望它能夠被強制執行於其他類型的方面。 – supercat

+1

另外,在許多情況下,有一個抽象類型的合約指定了不變性可能會有用。例如,可以有一個抽象的ImmutableMatrix類型,給定一個座標對,返回一個「double」。可以派生出一個'GeneralImmutableMatrix',它使用一個數組作爲後備存儲,但也可能有例如'ImmutableDiagonalMatrix',它只是沿着對角線存儲一系列項目(讀取項目X,Y將產生Arr [X](如果X == y,否則爲零)。 – supercat

+1

我最喜歡這個解釋。總是讓一個不可變的類「final」限制它的用處,特別是當你設計一個API來擴展時。爲了線程安全性,儘可能使你的類不可變,但保持它的可擴展性是有意義的。你可以將字段保護爲final,而不是private final。然後,明確建立一個關於遵守不變性和線程安全保證的子類的合同(在文檔中)。 – curioustechizen

9

不要標記整個班最後。

有一些正確的理由讓一個不可變的類可以像其他一些答案中所述的那樣擴展,所以將類標記爲final並不總是一個好主意。

最好是將您的物業標記爲私人物品和最終物品,如果您希望保護「合同」將您的獲得者標記爲最終物品。

通過這種方式,您可以允許該類進行擴展(可能甚至可以通過可變類),但是類的不可變方面會受到保護。屬性是私有的,不能訪問,這些屬性的獲取者是最終的,不能被覆蓋。

使用不可變類的實例的任何其他代碼將能夠依賴於類的不可變方面,即使它傳遞的子類在其他方面是可變的。當然,因爲它需要你的班級的一個實例,它甚至不知道這些其他方面。

+0

我寧願將所有不可變的方面封裝在一個單獨的類中,並通過構圖來使用它。在單個代碼單元中混合可變和不可變狀態會比較容易推理。 – toniedzwiedz

+0

完全同意。如果您的可變類包含您的不可變類,構圖將是實現此目的的一種非常好的方法。如果你的結構是另一種方式,這將是一個很好的部分解決方案。 –

1

對於創建不可變類,不強制將該類標記爲final。

讓我從java類本身的「BigInteger」類中的一個例子是不可變的,但它不是最終的。

實際上,不變性是一個概念,根據哪個對象創建的概念,它不能被修改。

讓我們從JVM的角度來看,從JVM的角度來看,所有線程必須共享對象的同一副本,並且在任何線程訪問它之前完全構造並且對象的狀態在其構建後不會改變。

不變性意味着沒有辦法喲改變對象的狀態,它一旦建立,這是由三個拇指規則,這使得編譯器識別類是不可變的實現,有如下幾點: -

  • 所有非私有字段應該被最終
  • 確保有在類沒有方法可以改變直接或間接
  • 的對象的字段的類中定義的任何對象引用不能從外部修改

欲瞭解更多信息,請參閱以下網址

http://javaunturnedtopics.blogspot.in/2016/07/can-we-create-immutable-class-without.html

0

設計本身沒有價值。設計總是用來實現一個目標。這裏的目標是什麼?我們是否想要減少代碼中的驚喜數量?我們是否想要防止錯誤?我們是否盲目遵守規則?

另外,設計總是要付出代價的。 Every design that deserves the name means you have a conflict of goals。記住

有了這一點,你需要找到這些問題的答案:

  1. 多少明顯的錯誤將在預防?
  2. 這可以防止多少微妙的錯誤?
  3. 這會使其他代碼更復雜(=更容易出錯)的頻率如何?
  4. 這是否使測試更容易或更難?
  5. 您項目中的開發人員有多好?他們需要多少指導才能使用大錘?

假設您的團隊中有許多初級開發人員。他們會拼命嘗試任何愚蠢的事情,只是因爲他們不知道解決問題的好方法。讓類最終可以防止錯誤(好),但也可以讓他們想出「聰明」的解決方案,例如在代碼中將所有這些類複製到可變的類中。

。另一方面,這將是很難使一個類final它正在使用無處不在後,但它很容易使一個final類非final以後,如果你發現你需要擴展它。

如果您正確使用接口,您可以通過始終使用接口,然後在需要時添加可變實現來避免「我需要使這個可變」問題。

結論:這個答案沒有「最佳」解決方案。這取決於你願意支付哪個價格以及哪些價格。

相關問題