使用不可变对象模拟属性更改

时间:2015-01-23 21:45:17

标签: java immutability

搜索后我发现了这些问题,但没有回答我的具体问题:

Undo/Redo with immutable objects

Why continue to use getters with immutable objects

How to write a test friendly immutable value class

Is it okay to expose the state of an immutable object

以下是设置: 我有一个像这样的不可变类:

public class MyImmutableOb {
    private final String name;
    private final Collection<Integer> myNumbers;

    public MyImmutableOb(String name) {
        this.name = name;
        myNumbers = new LinkedList<>();
    }

    public MyImmutableOb(String name, MyImmutableOb oldOb) {
        this.name = name;
        myNumbers = new LinkedList<>();
        for (int i : oldOb.getNumbers()) myNumbers.add(i);
    }

    public Collection<Integer> getNumbers() {
        return new LinkedList<>(myNumbers);
    }
}

我已经包含了这样的构造函数,意味着允许模拟对象的名称更改。问题是,以这种方式复制Collection是否会破坏不变性?:

public MyImmutableOb(String name, MyImmutableOb oldOb) {
    this.name = name;
    myNumbers = new LinkedList<>();
    for (int i : oldOb.getNumbers()) myNumbers.add(i);
}

我正在艰难地围绕着这个问题。

1 个答案:

答案 0 :(得分:2)

正如您所发现的,为了使您的类的实例不可变,您必须明确地将不可变性写入您的类;对象类型(与原始类型相对)本质上是可变的java.util中的集合类当然是可变的。像String和原始装箱类型IntegerDouble等Java类被编写为不可变的。

您的变量声明

private final String name;

是不可变的,因为它被声明为final ,因为String个对象是不可变的。

对变量使用final关键字可确保只为变量赋值一次,因此它是设计不可变类的绝佳工具。但是,这还不够,因为如果将变量分配给可变对象,那么 对象中的变量仍然可以被修改。

您的变量声明

private final Collection<int> myNumbers;

不保证不变性,因为虽然myNumbers只能分配一次,但分配给它的集合仍然是可变的。

好消息是,Java Collections实用程序类提供了轻量级包装器,使您的集合不可变。这将需要更改您的第一个构造函数

public MyImmutableOb(String name, int... numbers) {
    this.name = name;
    this.myNumbers = Collections.unmodifiableList(Arrays.asList(numbers));
}

这样myNumbers现在真正不可变。请注意,我已经放弃了对LinkedList的使用,因为当List中的MyImmutableOb不可变时,使用该专门的public MyImmutableOb(String name, MyImmutableOb oldOb) { this.name = name; this.myNumbers = oldOb.myNumbers; } 实现是没有意义的。

因此你的第二个构造函数变为

oldOb

因为myNumbers的{​​{1}}已经是不可变的,所以复制没有意义。

最后,我意识到你可能确实希望在构建myNumbers之后能够更改MyImmutableOb的内容。显然,使类真正不可变意味着在构造之后其实例中不会发生任何变化。