时间:2011-05-26 10:39:31

标签: java object immutability member getter-setter

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

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

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

10 个答案:

答案 0 :(得分:42)

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

即使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已经是不可变的(不可修改的),就像你将它变成一个不可修改的列表一样。

比较:

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

答案 1 :(得分:18)

您将返回对list的引用。而list并非一成不变。


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

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

或者您可以返回unmodifiable list

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

答案 2 :(得分:3)

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

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

如果您想在列表中使用只读视图,请查看Collections.unmodifiableList。这不会阻止列表包装当然不会改变,但它会阻止任何只有对不可修改列表的引用的人修改内容。

对于真正的不可变列表,请查看GuavaImmutableList类。

答案 3 :(得分:2)

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

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

 list = Collections.unmodifiableList(list);

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

答案 4 :(得分:2)

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

答案 5 :(得分:0)

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

由于你没有制定者,它的对象仍然保持完整。但你要做的是删除/添加新的/不同的对象,这很好。

答案 6 :(得分:0)

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

答案 7 :(得分:0)

要获得真正不可变的列表,您必须制作列表内容的深层副本。 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;
    }
}

答案 8 :(得分:0)

这对我有用。我们需要将类设为 final,这样它就不能被继承。将列表保留为最终版本(这只是对列表的引用)。在构造函数中,使用 unmodifiableList 并创建一个新列表并为其分配一个引用,而不是输入。

public final class ImmutableClassWithList {

private  final List<String> list;

public ImmutableClassWithList(ArrayList<String> input) {
    list = Collections.unmodifiableList(new ArrayList<String>(input));
}

public List<String> getList(){
    
    return list;
}

}

答案 9 :(得分:-1)

另一个解决方案是返回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());

    }
}