【问题标题】:Why is this class considered mutable? [duplicate]为什么这个类被认为是可变的? [复制]
【发布时间】:2017-08-31 06:25:09
【问题描述】:
public class A {
    private int[] values;

    public int[] getValues() {
        return values;
    }
}

我正在阅读的一本书说它不是不可变的,因为 values 是一种引用类型。除了在类中创建一个 main 方法之外,我认为没有办法改变 values

【问题讨论】:

    标签: java arrays immutability mutability


    【解决方案1】:

    它是可变的,因为你可以通过你暴露的方法改变values的内容

    A obj = new A(...) // values gets filled here
    int[] leak = obj.getValues()
    leak[0] = 42
    

    这意味着您的values 属性现在包含已在外部修改的信息。为了使它不可变,你应该返回一个数组的副本,这样你就可以确定没有外部代理可以修改它,你应该使它成为最终的,这样就不能创建可以暴露状态并使其再次可变的子类:

    public final class A {
      private int[] values;
    
      public int[] getValues() {
        return Arrays.copyOf(values);
      }
    }
    

    【讨论】:

    • 如何为空数组的元素赋值?
    • 我的意思是 values 不是最终的,因此引用将以 null 开头,但可以设置为类的另一部分中的任何内容。
    • 但在这种情况下,我们可以通过反射轻松更改值,不是吗?我觉得应该分不可变类和不可变对象,因为这个是不可变类,但不是不可变对象,因为values不是final。即使作者假设我们在其他地方更改了值,也意味着我们有方法或构造函数。如果是构造函数可能没问题(生成了值,但仍然是..reflection),如果是方法,我们可以轻松更改它。
    • @egorlitvinenko 您的“不可变”版本以何种方式免受反射修改?
    • @AndyTurner 在 JVM 中启用 SecurityManager 后,您无法通过反射更改最终字段。你还知道别的吗?
    【解决方案2】:

    它实际上不是通过values 数组可变的,正如其他答案中所述:该数组是null,因为它从未初始化,并且您不能通过getter 的结果使其非空。

    它是可变的,因为您可以对其进行子类化,并添加可变状态:

    class AA extends A {
      String foo;
    }
    
    AA aa = new AA();
    aa.foo = "foo";
    A a = aa;
    aa.foo = "bar";  // Mutates the state of a.
    

    请注意,Oracle Java 教程中有一个"Strategy for defining immutable objects",以及Effective Java 2nd Ed第 15 条:“最小化可变性”中的不可变对象所需属性的列表。 p>

    【讨论】:

    • int[] array = (new A()).getValues(); array = new int[5](); 数组本身也是可变的...
    • @geneSummons 嗯嗯。 array 的值是多少?
    • 好吧,从技术上讲,这是正确的。但在任何现实世界的程序中,values 可能会填充一些调用者可以修改的数据。所以是的,因为那个类是不可变的(只要你把它设为final),但它在实践中仍然没有真正设计成不可变
    • 为什么要创建另一个类并假设它仍然应该是不可变的?
    • @AndyTurner 我不知道 ideone.com 默认关闭了断言。我也不知道我的本地 IDE 还默认关闭了用于调试 Java 项目启动的断言。我站在正确的老师。
    【解决方案3】:

    values,作为数组是引用类型。你可以找到更多关于参考here。这意味着当getValues() 返回values 时,它实际上是在返回一个指向它的指针(每次都会取消引用,因为Java 没有显式指针)。虽然引用本身不能重新分配,因为values 是私有的,但内容可以更改。

    所以像a.getValues()[0]++ 这样的东西会增加values 的第一个元素,假设它不为空。

    【讨论】:

      【解决方案4】:

      您可以获取参考和更改元素数组:

       A.getValues()[0] = -100500
      

      您可以使用反射来更改值字段,或其他地方(可能是作者的意思)或使用继承,如下所示。

      例如,

      不可变类

      import java.util.Arrays;
      
      public final class A {
          private int[] values;
      
          public int[] getValues() {
              return values;
          }
      }
      

      不可变对象

      import java.util.Arrays;
      
      public class A {
          private final int[] values;
      
          public A(int[] values) {
              this.values = null == values ? null : Arrays.copyOf(values);
          }
      
          public int[] getValues() {
              return Arrays.copyOf(values);
          }
      }
      

      不可变的类和对象

      import java.util.Arrays;
      
      public final class A {
          private final int[] values;
      
          public A(int[] values) {
              this.values = null == values ? null : Arrays.copyOf(values);
          }
      
          public int[] getValues() {
              return Arrays.copyOf(values);
          }
      }
      

      【讨论】:

      • 这不是一成不变的。您需要在 ctor 中获取 values 的防御性副本,创建类 final
      • 是的,你是对的,我更正了。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-10-01
      • 1970-01-01
      • 1970-01-01
      • 2018-02-09
      相关资源
      最近更新 更多