具有ArrayList成员变量的不可变对象 – 为什么可以更改此变量?

我有一个包含各种成员变量的类。 有一个构造函数,有getter方法,但没有setter方法。 实际上,这个对象应该是不可变的。

public class Example { private ArrayList list; } 

现在我注意到以下内容:当我使用getter-method获取变量列表时,我可以添加新值等等 – 我可以更改ArrayList 。 当我为下一次调用此变量的get() ,返回更改的ArrayList 。 怎么会这样? 我没有再设置它,我只是努力了! 使用String这种行为是不可能的。 那么这里的区别是什么?

仅仅因为列表的引用是不可变的并不意味着它引用的列表是不可变的。

即使listfinal也可以这样做

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

但这不允许:

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

为了使列表不可变(也防止第一行),我建议你使用Collections.unmodifiableList

 public class Example { final private ArrayList list; Example(ArrayList listArg) { list = Collections.unmodifiableList(listArg); } } 

(请注意,这会创建一个不可修改的列表视图。如果有人持有原始引用,那么仍然可以通过该列表修改列表。)


使用String这种行为是不可能的。 那么这里的区别是什么?

这是因为String已经是不可变的(不可修改的)就像列表将其转换为unmodifiableList一样。

比较:

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

您将返回对list的引用。 list不是一成不变的。


如果您不希望更改列表,请返回它的副本:

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

或者您可以返回不可修改的列表 :

 public List get() { return Collections.unmodifiableList(this.list); } 

关键是要了解您没有更改字符串 – 您正在更改列表包含的字符串引用

换句话说:如果我从你的钱包中取出一美元,并用一角硬币替换它,我没有改变美元或硬币 – 我只是改变了钱包的内容。

如果要在列表中使用只读视图,请查看Collections.unmodifiableList 。 这不会阻止它自动更改的列表,但它会阻止只有对不可修改列表的引用的任何人修改内容。

对于一个真正不可变的列表,请查看Guava的ImmutableList类。

正如其他答案所说,从getter返回的Object仍然是可变的。

您可以通过使用Collections类对其进行装饰来使List不可变:

  list = Collections.unmodifiableList(list); 

如果将此内容返回给客户,则无法向其添加或删除元素。 然而,他们仍然可以从列表中获取元素 – 所以你必须确保它们也是不可变的,如果那就是你所追求的!

Collections.unmodifiableList()使列表无法定义。 这又创建了一个新的最终数组列表并覆盖了add,remove,addall和clear方法以抛出不支持的操作exception。 它是Collections类的黑客。 但是在编译时它并不会阻止你添加和删除东西。 我宁愿选择克隆该列表。 这可以帮助我保持现有对象不可变,并且不会花费我创建新列表。 阅读克隆和新运算符之间的区别( http://www.javatpoint.com/object-cloning )。 还有助于在运行时崩溃我的代码。

因此,如果要保护列表不被更改,则不应为列表提供getter方法。

它的对象仍然保持完好,因为你没有setter。 但你要做的是删除/添加新的/不同的对象,这很好。

列表引用是不可变的,但不是列表。 如果您希望列表本身是不可变的,请考虑使用ImmutableList

要获得一个真正不可变的列表,您必须制作列表内容的深层副本。 UnmodifiableList只会使引用列表有些不可变。 现在制作List或数组的深层副本对于内存越来越大而言将是艰难的。 您可以使用序列化/反序列化并将数组/列表的深层副本存储到临时文件中。 由于成员变量需要不可变,因此setter将不可用。 getter会将成员变量序列化为一个文件,然后对其进行desialize以获得一个深层副本。 Seraialization具有进入对象树深处的天生特性。 这样可以确保在某些性能成本下完全不变。

 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; } } 

另一种解决方案是返回arraylist的副本,而不是像这段代码那样返回实际的数组列表

 import java.util.ArrayList; import java.util.List; public final class ImmutableExample { private final List strings = new ArrayList(); public ImmutableExample() { strings.add("strin 1"); strings.add("string 2"); } public List getStrings() { List newStrings = new ArrayList(); 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()); } }