2011-05-26 100 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

相关问题