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

发布于 2024-11-09 20:07:41 字数 364 浏览 4 评论 0 原文

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

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

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

I have got one class with various member variables. There is a constructor and there are getter-methods, but no setter-methods. In fact, this object should be immutable.

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

Now I noticed the following: when I get the variable list with a getter-method, I can add new values and so on - I can change the ArrayList. When I call the next time get() for this variable, the changed ArrayList is returned. How can this be? I didn't set it again, I just worked on it!
With a String this behaviour isn't possible. So what is the difference here?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(11

无法言说的痛 2024-11-16 20:07:41

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

即使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.unmodifyingList:(

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                          |
           '-------------------------+------------------------------------'

Just because the reference to the list is immutable doesn't mean that the list it refers to is immutable.

Even if list was made final this would be allowed

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

but this would not allowed:

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

In order to make the list immutable (prevent also the first line), I suggest you use Collections.unmodifiableList:

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

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

(Note that this creates an unmodifiable view of the list. If someone is holding on to the original reference, then the list can still be modified through that.)


With a String this behaviour isnt possible. So what is the difference here?

That is because a String is already immutable (unmodifiable) just as the list would be if you turned it into an unmodifiableList.

Comparison:

              String data structure  | List data structure
           .-------------------------+------------------------------------.
Immutable  | String                  | Collection.unmodifiableList(...)   |
-----------+-------------------------+------------------------------------|
Mutable    | StringBuffer            | ArrayList                          |
           '-------------------------+------------------------------------'
山田美奈子 2024-11-16 20:07:41

您将返回对列表的引用。并且 list 并不是一成不变的。


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

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

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

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

You are returning a reference to the list. And list isn't immutable.


If you do not want your list to be changed return a copy of it:

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

Or you can return a unmodifiable list:

public List<String> get() {
    return Collections.unmodifiableList(this.list);
}
吃兔兔 2024-11-16 20:07:41

关键是要明白您不是在更改字符串 - 您正在更改引用列表中包含的字符串

换句话说:如果我从你的钱包里取出一美元,然后用一毛钱代替,那么我既没有改变这美元,也没有改变一毛钱——我只是改变了你钱包里的东西。

如果您想要列表的只读视图,请查看Collections.unmodifyingList。当然,这不会阻止它所包装的列表发生更改,但它会阻止任何仅引用不可修改列表的人修改内容。

要获得真正的不可变列表,请查看 GuavaImmutableList 类。

The key is to understand that you're not changing the string - you're changing which string references the list contains.

To put it another way: if I take a dollar out of your wallet, and replace it with a dime, I haven't changed either the dollar or the dime - I've just changed the contents of your wallet.

If you want a read-only view on the list, look at Collections.unmodifiableList. That won't stop the list that it's wrapping from changing of course, but it will stop anyone who only has a reference to the unmodifiable list from modifying the contents.

For a truly immutable list, look at Guava's ImmutableList class.

深巷少女 2024-11-16 20:07:41

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

您可以通过使用 Collections 类修饰 List 将其变为不可变:

 list = Collections.unmodifiableList(list);

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

As the other answers say the Object you return from the getters is still mutable.

You can turn the List immutable by decorating it using the Collections class:

 list = Collections.unmodifiableList(list);

If you return this to clients they will not be able to add or remove elements to it. However, they can still get elements out of the list - so you have to make sure they're immutable too, if that's what you're after!

烟─花易冷 2024-11-16 20:07:41

Collections.unmodificList() 使列表不可修改。这再次创建一个新的最终数组列表并重写 add、remove、addall 和clear 方法以抛出不支持的操作异常。它是 Collections 类的 hack。但在编译时,它不会阻止您添加和删除内容。我宁愿克隆该列表。这可以帮助我保持现有对象不可变,并且不需要我创建新列表。阅读克隆和新运算符之间的区别(http://www.javatpoint.com/object-cloning )。也有助于防止我的代码在运行时崩溃。

Collections.unmodifiableList() makes list unmidifiable. Which again creates a new final array list and override add, remove, addall and clear method to throw unsupportedoperationexception. Its a hack of Collections class. But at compile time it does not stop you from adding and removing stuff. I would rather go with cloning of that list. Which can help me to keep my existing object immutable and does not cost me creating of new list. Read difference between cloning and new operator(http://www.javatpoint.com/object-cloning). Also will help from crashing my code at runtime.

鞋纸虽美,但不合脚ㄋ〞 2024-11-16 20:07:41

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

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

}

This works for me. We need to have the class as final so that it can not be inherited. keep the list as final (this is just a reference to the list). In the constructor, use unmodifiableList and create a new list and assign a reference to that and not the input.

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

}
赠佳期 2024-11-16 20:07:41

从 Java 10 开始,您可以使用 List.copyOf 静态方法。

public void setList(List<T> list){
  this.list = List.copyOf(list);
}

根据 javadoc ,结果列表是不可变的,对基础列表的任何更改都不会影响新列表。

Since Java 10 you can use List.copyOf static method.

public void setList(List<T> list){
  this.list = List.copyOf(list);
}

As per javadoc, the resulting list is immutable, and any changes to the underlying list will not affect the new one.

还给你自由 2024-11-16 20:07:41

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

由于没有设置器,它的对象仍然保持完整。但是你所做的就是向其中删除/添加新的/不同的对象,这很好。

Therefore you should not provide a getter method for the list if you want to protect it from being changed.

Its objects are still staying intact since you didn't have setters. But what you do is remove/add new/different objects to it and this is fine.

思念绕指尖 2024-11-16 20:07:41

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

The list reference is immutable, but not the list. If you want the list itself to be immutable, consider using ImmutableList

看海 2024-11-16 20:07:41

要获得真正不可变的列表,您必须对列表的内容进行深层复制。 UnmodifierList 只会使引用列表在某种程度上不可变。
现在,随着大小的增加,对列表或数组进行深度复制将在内存上变得困难。
您可以利用序列化/反序列化并将数组/列表的深层副本存储到临时文件中。由于成员变量需要不可变,因此设置器将不可用。 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;
    }
}

To get a really immutable list, you will have to make deep copies of the contents of the list. UnmodifiableList would only render the list of references somewhat immutable.
Now making a deep copy of the List or array will be tough on memory with the growing size.
You can make use of serialization/deserialization and store the deep copy of array/list into a temp file. The setter would not be available as the member varaible needs to be immutable. The getter would serialize the member variable into a file and then desialize it to get a deep copy. Seraialization has an innate nature of going into the depths of an object tree. This would ensure complete immutability at some performance cost though.

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;
    }
}
雨落星ぅ辰 2024-11-16 20:07:41

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

    }
}

One another solution is to return copy of the arraylist not the actual array list like this code

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());

    }
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文