2015-12-05 35 views
2

我知道所有基本規則來使我們的類不可變,但是當有另一個類引用時我有點困惑。我知道如果有收集而不是Address那麼我們可以利用Collections.unmodifiableList(new ArrayList<>(modifiable));然後我們可以讓我們的類不可變。但在下面的情況下,我仍然無法得到這個概念。我們如何保持一個具有可變引用的類的不變性

public final class Employee{ 
    private final int id; 
    private Address address; 
    public Employee(int id, Address address) 
    { 
     this.id = id; 
     this.address=address; 
    } 
    public int getId(){ 
     return id; 
    } 
    public Address getAddress(){ 
     return address; 
    } 
} 

public class Address{ 
    private String street; 
    public String getStreet(){ 
     return street; 
    } 
    public void setStreet(String street){ 
     this.street = street; 
    } 
} 
+0

'Employee'並不是一成不變的,而且也沒有什麼可以做改變,如果你不能改變'Address'類來支持最低限度的複製,但最好首先使'Address'不變。 –

回答

3

那麼,這個概念正在閱讀JLS並理解它。在這種情況下,JLS說:

final字段還允許程序員在沒有同步的情況下實現線程安全的不可變對象。線程安全的不可變對象被所有線程看作是不可變的,即使使用數據競爭將線程間不可變對象的引用傳遞給線程。這可以提供安全保證,防止錯誤或惡意代碼濫用不可變類。必須正確使用最終字段以提供不變性保證。

最終字段的使用模型很簡單:在該對象的構造函數中設置對象的最終字段;並且在對象的構造函數完成之前,不要在另一個線程可以看到它的地方寫入對正在構造的對象的引用。如果遵循這一點,那麼當另一個線程看到該對象時,該線程將始終看到該對象的最終字段的正確構造版本。它還會看到那些最終字段所引用的任何對象或數組的版本,這些字段至少與最終字段一樣是最新的。

所以,你需要:

  1. address雙方最終和私人。
  2. 對於任何可變對象,必須防止對該對象的引用在外部看到。

在這種情況下,#2可能意味着您不能像getAddress()那樣返回對Address的引用。 你必須做一個防禦性的副本。即,製作該對象的副本,並將該副本存儲在員工中。如果您無法制作防禦性副本,那麼確實無法讓員工不可變。

public final class Employee{ 
    private final int id; 
    private final Address address; 
    public Employee(int id, Address address) 
    { 
     this.id = id; 
     this.address=new Address(); // defensive copy 
     this.address.setStreet(address.getStreet()); 
    } 
    pulbic int getId(){ 
     return id; 
    } 
    public Address getAddress() { 
     Address nuAdd = new Address(); // must copy here too 
     nuAdd.setStreet(address.getStreet()); 
     return nuAdd; 
} 

實施clone()或類似的東西(副本構造函數)將使創建的防守對象更容易爲複雜的類。但是,我認爲最好的建議是使Address不可變。一旦你這樣做,你可以自由傳遞其參考,而不會有任何線程安全問題。

在這個例子中,我注意到不是必須複製street的值。 Street是一個字符串,字符串是不可變的。如果street由可變字段(例如整數街道號)組成,那麼也必須製作street的副本,等等也是無限的。這就是爲什麼不可變對象非常有價值,它們打破了「無限複製」鏈。

+0

馬克,請你告訴我如何製作地址的防禦副本。一個例子會非常有幫助 – user1111880

+0

偉大的標誌,我以某種方式得到的概念,但因爲我們已經在Employee()構造函數中創建Address對象,那麼爲什麼我們要在getAdress()方法中再次創建它 – user1111880

+0

我說過爲什麼,至少兩次。 JLS說Employee對象不是不可變的,除非我們阻止看到內部引用。如果我們使用傳遞給ctor的值,那麼任何人都可以持有該引用。因此,我們不能使用它,我們必須製造我們自己的目標,任何人都看不到。 – markspace

0

所以在你的例子Employee類是不可變的,因爲它一旦建立,你不能改變它的狀態,因爲它只有getter方法。

Address等級爲mutable,因爲您可以使用setStreet方法對其進行修改。

因此,如果您有其他使用Address對象的類,則確定該類不能修改對象狀態。

4

那麼有由Java docs

的策略來定義不可改變提供的步驟對象

以下規則定義創建不變 對象的簡單策略。並非所有記錄爲「不可變的」的類都遵循這些規則。 這並不一定意味着這些類的創建者是馬虎 - 他們可能有充分的理由相信他們的類的實例在構建之後永遠不會改變。但是,這種策略需要複雜的分析,不適合初學者。

  • 不提供「二傳手」的方法 - 即修改字段或對象 田野提到的方法。
  • 使所有字段最終和私人。
  • 不允許 子類覆蓋方法。最簡單的方法是 將該類聲明爲final。更復雜的方法是將構造函數設爲私有,並在工廠方法中構造實例。
  • 如果 實例字段包括對可變對象的引用,不允許改變 這些對象:
    • 不提供修改 可變對象的方法。
    • 不要共享對可變對象的引用。從不 存儲對傳遞給構造函數 的外部可變對象的引用;如有必要,創建副本,並存儲對 副本的引用。同樣,當需要 時,請創建內部可變對象的副本,以避免在方法中返回原件。

Address類是可變的,因爲你可以用setStreet方法進行修改。 所以其他類可以修改這個類。

我們可以通過在獲取地址實例的副本時進行防禦,而不是信任給定實例的引用。

使Address對象最終

private final Address address; 

其次,

this.address = new Address(address.getStreet()); 

創建Address類,設置Street.Remove setter方法爲街道構造。

最後,而不是

public Address getAddress(){ 
    return address; 
} 

使用

public Address getAddress(){ 
    return new Address(address.getStreet()); 
} 
+0

是的,我知道,但我們怎麼能實施最後2點在我的情況。你能告訴我 – user1111880

+0

@ user1111880我已更新答案...請參閱http://www.javaranch.com/journal/2003/04/immutable.htm也 – Naruto

+0

@ user1111880如果您的問題是,請將解決方案標記爲「已接受」這樣可以幫助別人尋找類似的問題 – Naruto

0

如果你想封裝一個可變對象爲不可變的一個,那麼你需要:

  1. 創建一個可變對象副本(ie通過拷貝構造函數,克隆,序列化/反序列化等); 從不存儲對原始可變對象的引用。
  2. 永遠不會返回可變對象。如果必須,則返回該對象的副本
  3. 避免可以改變可變對象的方法。

公務員(INT ID,地址地址){

 this.id = id; 
     this.address=new Address(); 
     this.address.setStreet(address.getStreet()); 
    } 



public Address getAddress() { 
     Address nuAdd = new Address(); // must copy here too 
     nuAdd.setStreet(address.getStreet()); 
     return nuAdd; 
} 
相關問題