Java不可变类规则

Java不可变类规则,java,immutability,Java,Immutability,下面的类是不可变的: final class MyClass { private final int[] array; public MyClass(int[] array){ this.array = array; } } 不,这不是因为数组的元素仍然可以更改 int[] v1 = new int[10]; MyClass v2 = new MyClass(v1); v1[0] = 42; // mutation visible to MyClass

下面的类是不可变的:

final class MyClass {
    private final int[] array;
    public MyClass(int[] array){
        this.array = array;
    }
}

不,这不是因为数组的元素仍然可以更改

int[] v1 = new int[10];
MyClass v2 = new MyClass(v1);
v1[0] = 42;  // mutation visible to MyClass1

没有办法使数组不可变。也就是说,无法阻止任何客户端代码设置、删除或向数组中添加项

这里有一个真正不变的选择:

private static class MyClass
{
    private List<Integer> list;

    private MyClass(final int[] array)
    {
        final List<Integer> tmplist = new ArrayList<Integer>(array.length);
        for (int i : array)
        {
            tmplist.add(array[i]);
        }
        this.list = Collections.unmodifiableList(tmplist);
    }
}
私有静态类MyClass
{
私人名单;
私有MyClass(最终int[]数组)
{
最终列表tmplist=newarraylist(array.length);
for(int i:array)
{
tmplist.add(数组[i]);
}
this.list=Collections.unmodifiableList(tmplist);
}
}

我关于不变性规则的两分钱(我在阅读《有效Java——一本好书》时保留了这些规则!):

  • 不要提供可以修改对象状态的方法
  • 让你所有的领域都成为最终的
  • 确保您的类是不可扩展的
  • 让你所有的领域都成为私人领域
  • 提供对类中任何可以更改的字段或组件的独占访问。这基本上适用于您的情况(as)。使用您的类的人仍然有对您的数组的引用。相反的情况是返回对类的组件的引用。在这种情况下,始终创建防御副本。在您的情况下,不应分配引用。相反,将类用户提供的数组复制到内部组件中
  • “不变性”是程序员和他自己之间的约定。该约定可能或多或少由编译器强制执行

    如果类的实例在应用程序代码的正常执行过程中不发生更改,则它们是“不可变的”。在某些情况下,我们知道它们不会改变,因为代码实际上禁止它;在其他情况下,这只是我们如何使用类的一部分。例如,一个
    java.util.Date
    实例在形式上是可变的(在它上面有一个
    setTime()
    方法),但是习惯上处理它就像它是不可变的一样;这只是一个应用程序范围的约定,即不应调用
    Date.setTime()
    方法

    作为补充说明:

    • 不变性通常被认为是“外部特征”。例如,Java的
      字符串
      被证明是不可变的(Javadoc就是这么说的)。但是,如果您查看源代码,您将看到
      字符串
      实例包含一个名为
      哈希
      的私有字段,该字段可能会随着时间的推移而变化:这是对
      哈希代码()
      返回的值的缓存。我们仍然说
      String
      是不可变的,因为
      hash
      字段是一个内部优化,从外部看不到任何效果
    • 通过反射,可以修改最私有的实例字段(包括那些标记为
      final
      )的字段,如果程序员愿意的话。这并不是一个好主意:它可能会打破使用上述实例的其他代码片段所使用的假设。正如我所说,不变性是一种惯例:如果程序员想与自己抗争,那么他可以,但这可能会对生产率产生不利的副作用
    • 大多数Java值实际上都是引用。定义引用对象是否是你认为是“实例内容”的一部分取决于你。在类中,有一个字段引用(外部提供的)整数数组。如果以后修改了该数组的内容,你会认为这会破坏你的< MyClass > <代码>实例的不可变性吗?这个问题没有一般性的答案

      • 要使类不可变,您需要确保类上的所有字段都是最终字段,并且这些字段的类型也不可变

        这可能是一个痛苦的记忆,但有一个工具可以帮助你

        提供注释
        @ImmutableValue
        ,您可以将其添加到接口或类中

        有一个maven插件要在编译时检查您是否符合以下关于不变性的规则


        希望这有帮助。

        啊,对了!我总是忘记引用是分配给私有成员的。@Chaos:它不可能是包装器。它必须获取传入数组的副本。它不能包装原始调用方仍然可以变异的任何内容。@Jon-一个简单的语义失礼就够了。我的错误是认为一个副本是隐含的。您必须将对该数组的所有引用都保留在内部。您可以允许在不返回整个数组的情况下检索单个元素,也可以使用OO方法编程,方法是将需要访问数组的代码放在这个类本身中,使它既不可变又不可变。那么这是不可变的吗?最终类MyClass{private final int[]数组;public MyClass(int[]数组){this.array=array.clone();}}}否,不可修改列表和asList都创建视图。因此,对数组的外部更改(如JaredPar的回答中所述)将导致此版本的MyClass发生更改。
        Arrays.asList(array)
        也将返回一个
        List
        。曾经被否决的人不理解不可变契约。如果返回一个可以更改的数据结构,不管它是否是某个备份数组的副本,它仍然不是不可变的。克隆某个东西并返回一个可变克隆并不能使一个类100%不可变,这也违反了最不令人惊讶的原则。asList不创建“视图”,而是将数组的可变副本创建为列表。不可修改的只是,它返回一个不可修改的列表,因此是不可变的。这里的教训是不要使用数组和适当的集合类作为开始。不要实现
        Serializable