定义不可变对象的策略

strategy for defining immutable objects

我是 Java 的新手,正在尝试学习通过 Java tutorial Oracle 定义不可变对象的策略。由于我的知识有限,可能是措辞上的原因,我真的很难理解下面的段落,我只是想知道是否有人可以向我解释一下它的实际含义。在此先感谢您的帮助!

Don't share references to the mutable objects. Never store references to external, mutable objects passed to the constructor; if necessary, create copies, and store references to the copies. Similarly, create copies of your internal mutable objects when necessary to avoid returning the originals in your methods.

实际上它表示您不想将您的状态暴露给外界,因此外界可以根据需要修改您的内部状态。你不想要这个,因为在那个内部状态下这是你的事,有人可以改变它。这也是为什么它建议创建内部可变对象的副本并将它们作为副本传递到外部的原因。他们会改变这些副本,但不会对您的内部状态产生副作用。

示例:

/**
 * A simple mutable.
 */
class Mutable {

    private int n;

    public Mutable(int n) {
        this.n = n;
    }

    public int getN() {
        return n;
    }

    public void setN(int n) {
        this.n = n;
    }

    @Override
    public String toString() {
        return "Mutable{" + "n=" + n + '}';
    }

}

// A thing that holds things.
class Thing<T> {

    T m;

    public Thing(T m) {
        this.m = m;
    }

    public T getM() {
        return m;
    }

    @Override
    public String toString() {
        return "Thing{" + "m=" + m + '}';
    }

}

public void test() {
    // Never store references to external, mutable objects passed to the constructor
    Mutable m = new Mutable(10);
    // t10 holds a reference to my mutable `m`, currently containing `10`
    Thing<Mutable> t10 = new Thing<>(m);
    // Now `m` holds `50`, even the one in `t10`.
    m.setN(50);
    // Make the new one holding `m` at value `50` now.
    Thing<Mutable> t50 = new Thing<>(m);
    // Both print the same because they both hold the same `m`
    System.out.println("t10 = " + t10 + " t50 = " + t50);
    // We can even mess with it after the fact - this is why you should return a copy.
    t50.getM().setN(42);
    // Both print the same because they both hold the same `m`
    System.out.println("t10 = " + t10 + " t50 = " + t50);
}

这证明了所有三点。

  • 如果您将 mutable 传递给构造函数并将该可变变量保存在状态中,则对该可变变量的更改将更改对象的状态。

  • 如果你return一个mutable它也可以在你的对象之外被修改,从而改变你的状态。

要避免这种情况:

  • 如果可能,在所有情况下都使用 immutable
  • 如果您必须在构造函数中使用可变变量,请尽可能复制它。
  • 如果您 return 任何地方的可变文件,return 如果可能,请复制一份。

创建 Immutable 对象时,如果有 Date 对象等可变字段,则适用以下行:

Don't share references to the mutable objects. Never store references to external, mutable objects passed to the constructor; if necessary, create copies, and store references to the copies. Similarly, create copies of your internal mutable objects when necessary to avoid returning the originals in your methods.

同样可以实现如下:

  • 在创建不可变对象时创建 Date 对象的防御副本。
  • 不要为此日期对象提供任何 setter 方法。
  • 如果您需要提供 getter 方法,那么 getter 方法应该 return 日期字段的副本,因为日期本身是可变的。这里,clone() 方法有助于实现这一点。

现在,由于 class 中没有对 Date 字段的引用,因此 class 之外的任何内容都无法修改 Immutable class 的状态。

勾选Example for Immutable class

不共享对可变对象的引用

public class A {
  private List<Integer> privateRef;
  private int privateVal = 0;

  public List<Integer> bad() {
      return this.privateRef;
  }

  public int good() {
      return privateVal;
  }

}

bad() 方法不好,因为它公开了对私有成员的引用,从而使调用者能够这样做:

A a = new A();
List<Integer> extractedRef = a.bad();
extractedRef.add(1);

因此改变 a 的私有列表(如果您检查 a.privateList,您会看到它包含 1)。 A 不再控制其内部状态。

good() 方法很好,因为即使调用者这样做:

A a = new A();
int number = a.good();
number++;

即使数字为 1,a.privateVal 的值仍然为 0。

永远不要存储对传递给构造函数的外部可变对象的引用

假设我们将这个构造函数添加到 class:

public class A {
    ...
    public A(List<Integer> anotherList) {
        this.privateRef = anotherList;
    } 
    ...
}

我们处于类似情况:私有列表的突变可能发生在外部:

List<Integer> list = new ArrayList<Integer>()
A a = new A(list);
list.add(1);

我们改变了 a 的内部状态(如果您检查 a.privateList,您会看到它包含 1)。

如有必要,创建副本并存储对副本的引用。

也就是说,如果你希望 A 是不可变的,你应该这样做:

public A(List<Integer> anotherList) {
    this.privateRef = new ArrayList<>(anotherList);
} 

这样,A 的新实例会在创建时获得列表的副本,成为其内部状态的一部分。从这一点来看,对给定列表(示例中的 "list")的突变不会影响 a 的内部状态:只有 a 可以改变其私有列表。

同样,必要时创建内部可变对象的副本,以避免在方法中返回原始对象。

这就是您解决第一个示例的方法,如果 A 想要公开其内部可变列表,则不应这样做:

  public List<Integer> bad() {
      return this.privateRef;
  }

但是

  public List<Integer> better() {
      return new ArrayList<>(this.privateRef);
  }

此时

A a = new A();
List<Integer> extractedRef = a.better();
extractedRef.add(1)

此时,a.privateRef 仍然是空的(因此它的状态不受外部突变的影响)。但是 extractedRef 将包含 1.

补充一点。请注意,尽管 class 应用了上述所有原则:

public class A {
    private List<Integer> privateRef = new ArrayList<>();
    private int privateVal = 0;

    public A(List) {
        this.privateRef.addAll(this.privateRef);
    }

    public List<Integer> getList() {
        return new ArrayList<>(this.privateRef);
    }

    public int getVal() {
        return privateVal;
    }

    public void mutate() {
        this.privateVal++;
        this.privateRef.clear();
    }

}

(它不公开对可变对象的引用,也不保留对外部可变对象的引用),它并不是真正不可变的,因为有一种方法可以改变其内部状态(对其调用 mutate())。

您当然可以删除 mutate(),但更正确的不变性替代方案可能是:

public class A {
    private final List<Integer> privateRef = new ArrayList<>();
    private final int privateVal = 0;

    public A(List) {
        this.privateRef.addAll(this.privateRef);
    }

    public List<Integer> getList() {
        return new ArrayList<>(this.privateRef);
    }

    public int getVal() {
        return privateVal;
    }

}

(例子我没有编译,不过应该差不多可以了)