..自动具备线程安全性,无需同步问题
当两个不同的线程修改同一对象的状态时,就会出现并发问题。不可变对象无法被修改,因此没有问题。
例如:一个String
。两个线程可以传递相同的String
,因为它们都不能以任何方式改变它。
不需要复制构造函数
……因为复制是改变其唯一方法。对于不可变对象常见的设计模式是对每个“修改”操作进行复制,然后在新对象上执行操作。
复制构造函数通常用于您想要更改而不影响原始对象的对象。这在不可变对象的情况下始终如此(按定义)。
在String
的情况下,所有方法和+
运算符都返回新的String
。
不需要实现克隆
见上文。
作为字段使用时不需要进行防御性复制
曾经我做了一些愚蠢的事情。我有一个包含枚举值的列表:
private static final List<Status> validStatuses;
static {
validStatuses = new ArrayList<Status>();
validStates.add(Status.OPEN);
validStates.add(Status.REOPENED);
validStates.add(Status.CLOSED);
}
这个列表是从一个方法返回的:
public static List<Status> getAllStatuses() {
return validStates;
}
我已经获取了那个列表,但我只想在界面中显示开放的状态。
List<Status> statuses = Status.getAllStatuses();
statuses.remove(Status.CLOSED);
太好了,它起作用了!等等,现在所有的状态列表都只显示那两个 - 即使在页面刷新后也是如此!发生了什么?我修改了一个静态对象。糟糕。
我本可以对 getAllStatuses
返回对象使用防御性拷贝。或者,我一开始就可以使用像 Guava的ImmutableList 这样的东西:
private static final List<Status> validStatuses =
ImmutableList.of(Status.OPEN, Status.REOPENED, Status.CLOSED);
然后当我做了些傻事:
List<Status> statuses = Status.getAllStatuses();
statuses.remove(Status.CLOSED); // Exception!
始终具有“失败原子性”(由Joshua Bloch使用的术语):如果不可变对象抛出异常,则它永远不会处于不良或不确定状态。
因为该类永远无法被修改,所有通过修改发出的状态都是整体的,合格的对象(因为它们不能改变,它们必须始终处于合格状态才能有用)。异常不会发出新对象,因此您永远不会有不良或不确定的状态。