2011-05-26 25 views
14

我有一個類與各種成員變量。有一個構造函數,有getter方法,但沒有setter方法。事實上,這個對象應該是不可變的。帶有ArrayList成員變量的不可變對象 - 爲什麼可以更改此變量?

public class Example { 
    private ArrayList<String> list; 
} 

現在我注意到以下幾點:當我得到的變量列表用吸氣劑的方法,我可以添加新的價值觀等等 - 我可以改變ArrayList。當我爲這個變量調用下一次get()時,返回已更改的ArrayList。怎麼會這樣?我沒有重新設置,我只是在努力! 使用String這種行爲是不可能的。那麼這裏有什麼不同?

回答

35

只是因爲參考到列表是不可變的並不意味着列表它是指是不可變的。

即使list作出final這將允許

// changing the object which list refers to 
example.getList().add("stuff"); 

但這允許:

// changing list 
example.list = new ArrayList<String>(); // assuming list is public 

爲了使列表不可變的(預防也第一行),我建議你用Collections.unmodifiableList

public class Example { 
    final private ArrayList<String> list; 

    Example(ArrayList<String> listArg) { 
     list = Collections.unmodifiableList(listArg); 
    } 
} 

(請注意,這會創建一個不可修改的列表視圖。如果有人抱着原來的基準,則列表仍可以通過修改。)


用細繩這種行爲可能未啓用。那麼這裏有什麼不同?

這是因爲String已經是不可變的(不可修改)正如列表是,如果你把它變成一個unmodifiableList。

比較:

   String data structure | List data structure 
      .-------------------------+------------------------------------. 
Immutable | String     | Collection.unmodifiableList(...) | 
-----------+-------------------------+------------------------------------| 
Mutable | StringBuffer   | ArrayList       | 
      '-------------------------+------------------------------------' 
+0

AFAIK實際數組列表'Collections.unmodifiableList()'返回給定列表的不可變WRAPPER。如果我是正確的,上面不會保證不變性。一個類可以實例化一個列表,實例化Example,並且仍然可以通過修改傳遞給構造函數的原始列表來修改Example所持有的列表。雖然答案可能足以解決分歧,但它可能無法滿足嚴格的「不變性」要求。 – Maurice 2015-06-19 00:09:06

+2

這是正確的。答案已更新。你可以做'Collections.unmodifiableList(new ArrayList <>(listArg))'以確保沒有人持有對潛在可變列表的引用,從而避免可變性。 – aioobe 2015-06-19 07:26:41

15

您正在返回對list的引用。並且list不是不可變的。


如果您不希望您的列表被改變返回它的一個副本:

public List<String> get() { 
    return new ArrayList<String>(this.list); 
} 

或者你可以返回unmodifiable list

public List<String> get() { 
    return Collections.unmodifiableList(this.list); 
} 
3

的關鍵是要明白,你不改變 - 你改變該字符串引用列表包含。換句話說:如果我從你的錢包中取出一美元,並用一角硬幣代替它,我沒有改變美元或毛錢 - 我只是改變了錢包的內容。

如果您想在列表中顯示只讀視圖,請參閱Collections.unmodifiableList。這不會阻止列表改變當然包裝,但它會阻止任何人只有參考不可修改列表修改內容。

對於確實不變的列表,請看GuavaImmutableList類。

0

因此,如果您想保護它不被更改,則不應爲列表提供getter方法。

由於您沒有安裝程序,所以它的對象仍然保持完好無損。但是你要做的是刪除/添加新的/不同的對象,這很好。

2

正如其他答案所說,你從getters返回的Object仍然是可變的。

您可以打開列表,通過使用Collections類裝飾它一成不變:

list = Collections.unmodifiableList(list); 

如果你回到這個給客戶,他們將無法元素添加或刪除它。但是,他們仍然可以將列表中的元素排除在外 - 所以您必須確保它們不可變,如果這就是您要做的事情!

0

列表引用是不可變的,但不是列表。如果您希望列表本身是不可變的,請考慮使用ImmutableList

2

Collections.unmodifiableList()使列表不可被屏蔽。它再次創建一個新的最終數組列表並覆蓋add,remove,addall和clear方法來拋出不支持的操作。它是一個集合類的黑客攻擊。但在編譯時,它不會阻止你添加和刪除東西。我寧願去克隆這個列表。這可以幫助我保持現有的對象不變,並且不會花費我創建新列表。閱讀克隆和新操作員之間的區別(http://www.javatpoint.com/object-cloning)。也將有助於在運行時崩潰我的代碼。

0

爲了得到真正不變的列表,您必須對列表內容進行深層複製。 UnmodifiableList只會使引用列表變得不可變。 現在製作List或數組的深層副本將隨着規模的增長而變得越來越難記憶。 您可以使用序列化/反序列化並將數組/列表的深層副本存儲到臨時文件中。由於成員變量必須是不變的,因此變換器不可用。 getter會將成員變量序列化爲一個文件,然後將其去極化以獲得深層副本。太陽能化具有進入對象樹深處的天生性質。儘管如此,這將確保以某些性能成本完全不變。

package com.home.immutable.serial; 

import java.io.File; 
import java.io.FileInputStream; 
import java.io.FileNotFoundException; 
import java.io.FileOutputStream; 
import java.io.IOException; 
import java.io.ObjectInputStream; 
import java.io.ObjectOutputStream; 

public final class ImmutableBySerial { 

    private final int num; 
    private final String str; 
    private final TestObjSerial[] arr; 

    ImmutableBySerial(int num, String str, TestObjSerial[] arr){ 
     this.num = num; 
     this.str = str; 
     this.arr = getDeepCloned(arr); 
    } 

    public int getNum(){ 
     return num; 
    } 

    public String getStr(){ 
     return str; 
    } 

    public TestObjSerial[] getArr(){ 
     return getDeepCloned(arr); 
    } 

    private TestObjSerial[] getDeepCloned(TestObjSerial[] arr){ 
     FileOutputStream fos = null; 
     ObjectOutputStream oos = null; 
     FileInputStream fis = null; 
     ObjectInputStream ois = null; 
     TestObjSerial[] clonedObj = null; 
     try { 
      fos = new FileOutputStream(new File("temp")); 
      oos = new ObjectOutputStream(fos); 
      oos.writeObject(arr); 
      fis = new FileInputStream(new File("temp")); 
      ois = new ObjectInputStream(fis); 
      clonedObj = (TestObjSerial[])ois.readObject(); 

     } catch (FileNotFoundException e) { 
      e.printStackTrace(); 
     } catch (IOException e) { 
      e.printStackTrace(); 
     } catch (ClassNotFoundException e) { 
      e.printStackTrace(); 
     } finally { 
      try { 
       oos.close(); 
       fos.close(); 
      } catch (IOException e) { 
       e.printStackTrace(); 
      } 
     } 
     return clonedObj; 
    } 
} 
0

彼此溶液是返回該ArrayList副本不是這樣的代碼

import java.util.ArrayList; 
import java.util.List; 

public final class ImmutableExample { 

    private final List<String> strings = new ArrayList<String>(); 

    public ImmutableExample() { 
     strings.add("strin 1"); 
     strings.add("string 2"); 
    } 

    public List<String> getStrings() { 
     List<String> newStrings = new ArrayList<String>(); 
     strings.forEach(s -> newStrings.add(s)); 
     return newStrings; 
    } 

    public static void main(String[] args) { 
     // TODO Auto-generated method stub 
     ImmutableExample im = new ImmutableExample(); 
     System.out.println(im.getStrings()); 
     im.getStrings().add("string 3"); 
     System.out.println(im.getStrings()); 

    } 
} 
+0

這不會添加新的信息。在這個答案中已經解釋了返回副本:https://stackoverflow.com/a/6137267,它甚至使用複製清單的_much_清理程序版本。 – Tom 2017-11-14 15:23:40

相關問題