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

| 我有一类具有各种成员变量的类。有一个构造函数,有getter方法,但没有setter方法。实际上,该对象应该是不变的。
public class Example {
   private ArrayList<String> list; 
}
现在我注意到了以下几点:当我用getter方法获得变量列表时,可以添加新值,依此类推-可以更改
ArrayList
。当我下次调用此变量
get()
时,将返回更改后的
ArrayList
。怎么会这样?我没有再次设置它,我只是在做它! 使用
String
时,此行为是不可能的。那么,这里有什么区别?     
已邀请:
仅仅因为对列表的引用是不可变的,并不意味着它所引用的列表是不可变的。 即使
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
已经是不可变的(不可修改),就像将列表变成不可修改的列表一样。 比较:
              String data structure  | List data structure
           .-------------------------+------------------------------------.
Immutable  | String                  | Collection.unmodifiableList(...)   |
-----------+-------------------------+------------------------------------|
Mutable    | StringBuffer            | ArrayList                          |
           \'-------------------------+------------------------------------\'
    
您正在返回对“ 5”的引用。
list
不是一成不变的。 如果您不想更改列表,请返回它的副本:
public List<String> get() {
    return new ArrayList<String>(this.list);
}
或者您可以返回一个不可修改的列表:
public List<String> get() {
    return Collections.unmodifiableList(this.list);
}
    

bab

关键是要了解您没有更改字符串-您正在更改列表包含的字符串引用。 换句话说,如果我从您的钱包中拿出一美元,并用一角钱代替,那么我既没有改变美元,也没有改变一角钱-我只是改变了您钱包的内容。 如果要在列表上显示只读视图,请查看
Collections.unmodifiableList
。当然,这不会阻止它包装的列表的更改,但是它将阻止仅引用不可修改列表的任何人修改内容。 有关真正的不可变列表,请查看Guava的ImmutableList类。     
正如其他答案所说,您从吸气剂返回的对象仍然是可变的。 您可以通过使用Collections类装饰列表,使列表变为不可变的:
 list = Collections.unmodifiableList(list);
如果将其退还给客户,他们将无法向其添加或删除元素。但是,它们仍然可以从列表中删除元素-因此,如果您要这样做,则必须确保它们也是不可变的!     
Collections.unmodifiableList()使列表不可修改。它将再次创建一个新的最终数组列表,并覆盖add,remove,addall和clear方法以引发不支持的操作异常。这是一个Collections类的黑客。但是在编译时,它并不会阻止您添加和删除内容。我宁愿克隆该列表。这可以帮助我保持现有对象不变,而无需花费我创建新列表的费用。读取克隆和新运算符之间的区别(http://www.javatpoint.com/object-cloning)。这也将有助于在运行时使我的代码崩溃。     
因此,如果要保护列表不被更改,则不应为列表提供getter方法。 由于您没有二传手,它的对象仍然保持原样。但是您要做的是向其中删除/添加新的/不同的对象,这很好。     
列表引用是不可变的,但列表不是。如果您希望列表本身是不可变的,请考虑使用ImmutableList     
要获得一个真正不变的列表,您必须对列表内容进行深层复制。 UnmodifiableList只会使引用列表有些不可变。 现在,随着大小的增加,对列表或数组进行深拷贝将对内存造成困难。 您可以使用序列化/反序列化并将数组/列表的深层副本存储到临时文件中。该设置程序将不可用,因为成员变量需要不可变。 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;
    }
}
    
另一种解决方案是返回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());

    }
}
    

要回复问题请先登录注册