【问题标题】:Immutable Object with ArrayList member variable - why can this variable be changed?具有 ArrayList 成员变量的不可变对象 - 为什么可以更改此变量?
【发布时间】:2011-09-02 11:50:50
【问题描述】:

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

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

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

【问题讨论】:

    标签: java object immutability member getter-setter


    【解决方案1】:

    这对我有用。我们需要将类设置为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;
        }
    
    }
    

    【讨论】:

      【解决方案2】:

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

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

      【讨论】:

      • 这不会添加新信息。返回副本已在此答案中进行了解释:stackoverflow.com/a/6137267,它甚至使用了 much 更简洁的复制列表版本。
      【解决方案3】:

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

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

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

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

      【讨论】:

        【解决方案4】:

        要获得一个真正不可变的列表,您必须对列表的内容进行深度复制。 UnmodifiableList 只会使引用列表有些不可变。 现在,随着大小的增加,制作 List 或数组的深层副本将很难占用内存。 您可以利用序列化/反序列化并将数组/列表的深层副本存储到临时文件中。 setter 将不可用,因为成员变量需要是不可变的。 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;
            }
        }
        

        【讨论】:

          【解决方案5】:

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

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

          【讨论】:

          • AFAIK Collections.unmodifiableList() 为给定列表返回一个不可变的 WRAPPER。如果我是正确的,以上将不能保证不变性。一个类可以实例化一个列表,实例化 Example,并且仍然可以通过修改传递给构造函数的原始列表来修改 Example 所持有的列表。尽管答案可能足以解决差异,但它可能无法满足严格的“不变性”要求。
          • 没错。答案已更新。您可以使用Collections.unmodifiableList(new ArrayList&lt;&gt;(listArg)) 来确保没有人持有对底层可变列表的引用,从而避免可变性。
          【解决方案6】:

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

          【讨论】:

            【解决方案7】:

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

            【讨论】:

              【解决方案8】:

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

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

               list = Collections.unmodifiableList(list);
              

              如果您将此返回给客户,他们将无法向其中添加或删除元素。但是,它们仍然可以将元素从列表中取出 - 因此,如果您想要的是,您必须确保它们也是不可变的!

              【讨论】:

                【解决方案9】:

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

                由于没有设置器,它的对象仍然完好无损。但是你要做的是删除/添加新的/不同的对象,这很好。

                【讨论】:

                  【解决方案10】:

                  您正在返回对list 的引用。而list 也不是一成不变的。


                  如果您不想更改您的列表,请返回一份副本:

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

                  或者您可以返回unmodifiable list

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

                  【讨论】:

                    猜你喜欢
                    • 2011-05-13
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 2013-04-18
                    • 1970-01-01
                    • 1970-01-01
                    • 2020-04-15
                    相关资源
                    最近更新 更多