不要共享对可变对象的引用
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++;
即使 number 为 1,a.privateVal 的值仍为 0。
永远不要存储对传递给构造函数的外部可变对象的引用
假设我们将这个构造函数添加到我们的类中:
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 的新实例在创建时会获得列表的副本,该副本将成为其内部状态的一部分。从这一点来看,对给定列表(示例中的“列表”)的突变不会影响 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。
补充一点。请注意,即使此类应用了上述所有原则:
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;
}
}
(我还没有编译示例,但它们应该几乎没问题)。