【问题标题】:Is this java class immutable?这个java类是不可变的吗?
【发布时间】:2012-08-24 00:50:41
【问题描述】:

我正在实现一个不可变类,其结构如下:

public final class A{
    private final B bOb;
    public A(){
        bOb = new B();
    }
    public A(A a){
        bOb = new B(a.bOb);
    }
    public A addData(Type data){ // Type - Integer,String,char,etc.
        A newA = new A(this); //making a copy of the object that is calling addData method
        newA.bOb.add(data);
        return newA;
    }
}

这个实现是否正确?假设对象 bOb 是一个列表。

【问题讨论】:

标签: java immutability


【解决方案1】:

它不是完全不可变的,因为 B 本身似乎是可变的(通过 add 方法)并且 A 包含 B 的一个实例。

但我认为它实际上是不可变的(即,它的行为就像从外部观察者的角度来看是不可变的),前提是满足以下所有条件:

  • new B(B) 执行 B 的完整深层副本(如果没有,则 addData 可能会在原始 B 中发生变异)
  • 您没有通过任何其他方式泄露对 bOb 的引用
  • 自己添加的元素是不可变的

你可以从有效的不可变中获得不可变的大部分好处,所以我认为这个设计是可以的,只要 Type 是不可变的 - 在它的构造过程中改变一个对象是可以的,只要它在你传递给某人的引用后永远不会改变别的。一个很好的例子是java.lang.String - 在内部它包含一个可变数组,该数组在构造字符串时写入,但在那之后就不再更改。

【讨论】:

  • 因为在我的情况下 B 是一个 LinkedList (它是可变的),而我正在做 bOb = new B(a.bOb); ,也就是说,我没有为 bOb 使用任何 getter,我直接通过我的对象A a,应该是不可变的吧?
  • @sTEAK - LinkedList 不会进行深度克隆,因此如果它的任何内容是可变的,那么您将失去整体的不变性。如果您坚持 LinkedList 中的不可变内容,那么您应该没问题 - 但这取决于Type。附言可能是使用泛型的好地方:-)
  • 向 this 添加可变对象有什么解决方法吗?所以它仍然保持不变
  • @sTEAK。你不可能有一个不可变的对象,它接受外来对象并使其可观察状态以任何方式依赖于这些对象的状态。所以问题仍然悬而未决:A 的可观察状态是否以任何方式取决于 LinkedList 中对象的状态?
  • 防御性复制技术可以帮助您。例如,您可以添加一些接口Copyable<T extends Copyable<T>>,它将具有public T getCopy() 方法。你的类可以将此接口作为通用参数并将其用于防御性副本。但是这个解决方案有缺点——只有你的类可以实现这个接口。
【解决方案2】:

你的实现:

public A addData(int data){
    A newA = new A(this);
    newA.bOb.add(data); // <--- bOb is mutable
    return newA;
}

一个 bOb 正在被更改,因此如果您公开对 bOb 的任何直接访问,它可以被更改。 (因为你没有暴露任何关于 bOb 的信息,所以这个场景没有意义。)

提示:

  • Collections.emptyList() 返回一个不可变列表。
  • Collections.unmodifiableList() 返回给定列表的不可变浅拷贝。

考虑这种“更安全”的实现:

import java.util.*;

public final class A<T>{ //T must also be immutable (String, integer, char, ...)
    private final List<T> list;
    public A(){
        this.list = Collections.emptyList();
    }
    public A(List<T> list){
        this.list = Collections.unmodifiableList(list);
    }
    public A<T> addData(T data){
        List<T> shallowCopy = new ArrayList<T>(this.list);
        shallowCopy.add(data);
        return new A<T>(shallowCopy);
    }
    public List<T> getItems() {
        return this.list;
    }
}

【讨论】:

  • 这取决于new B(a.bOb);是如何实现的。还是不行?
  • 试试Collections.unmodifiableList()...还能用吗?
  • 在类中改变bOb 通常被认为是好的只要状态变化在A之外是不可见的。有关类似示例,请参阅 Scala 中不可变队列的 this description
【解决方案3】:

如果bOb 是一个列表并且它包含可变内容,那么不是。但是您似乎只使用int 作为内容,这解决了问题。

【讨论】:

  • 嗯 B 实际上是一个 LinkedList,我正在通过 addData 方法向列表添加一个通用值,所以它不一定是int。所以你的意思是说它不是一成不变的?
  • 例如,如果您将可变内容放入您的bOb(假设是另一个List),那么它可以被修改,这将影响A实例状态。
  • 但是如果我在列表中添加Integer 则抛出addData 方法,那么?
  • Integer 是不可变的,因此您的类对于这种类型将是不可变的。但是如果你将使用Date,例如,那么它可以被修改并影响实例状态。例如,A a=new A();Date d=new Date();a.addData(d);d.setTime(1L);。在最后一个运算符d 对象将被修改,a 对象状态也将被修改。但是这个修改不会影响可见状态,因为你的类没有它。这是有趣的部分——你的类没有可见的状态,所以它是无用的,但不可变的。如果您将共享其状态(包括bob 值),那么可见状态将受到影响。
  • 那么,实际上它在所有情况下都是不可变的吗?
【解决方案4】:

在这种形式下,是的,你的类是不可变的。

但是,这是一个简单的示例,实际上对任何事情都没有用处,因为您无法访问任何内部数据。您需要注意的是不要让任何对bOb 的引用从A 中逃逸。如果添加返回bOb 的方法,则需要返回bOb副本,以避免意外允许调用者更改A 的任何内容。

【讨论】:

    【解决方案5】:

    这取决于B(B b) 构造函数的工作方式。如果它是一个复制构造函数,它应该对b 字段进行深拷贝。在这种情况下,A 是不可变的。

    如果构造函数只是简单地引用同一个b 实例,对它的任何更改都会反映在bOb 属性上,因此A 类不是不可变的...

    【讨论】:

    • 好吧,所以使用LinkedList(Collection c) 可以获得具有相同元素的新列表。正如您所说 Type 是原始类型(或包装器),因此您的 A 类是不可变的。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-05-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多