最小化可变性
不可变类简单来说是它的实例不能被修改的类。
包含在每个实例中的所有信息在对象的生命周期中是固定的,因此不会观察到任何变化。
Java 平台类库包含许多不可变的类,包括 String
类,基本类型包装类以及 BigInteger
类和 BigDecimal
类。
有很多很好的理由:不可变类比可变类更容易设计,实现和使用。 他们不太容易出错,更安全。
要使一个类不可变,请遵循以下五条规则:
- 不要提供修改对象状态的方法(也称为 mutators)。
- 确保这个类不能被继承。 这可以防止粗心的或恶意的子类,假设对象的状态已经改变,从而破坏类的不可变行为。 防止子类化通常是通过
final
修饰类,但是我们稍后将讨论另一种方法。 - 把所有属性设置为 final。 通过系统强制执行,清楚地表达了你的意图。 另外,如果一个新创建的实例的引用从一个线程传递到另一个线程而没有同步,就必须保证正确的行为,正如内存模型所述。
- 把所有的属性设置为 private。 这可以防止客户端获得对属性引用的可变对象的访问权限并直接修改这些对象。 虽然技术上允许不可变类具有包含基本类型数值的公共
final
属性或对不可变对象的引用,但不建议这样做,因为它不允许在以后的版本中更改内部表示(详见第 15 和 16 条)。 - 确保对任何可变组件的互斥访问。 如果你的类有任何引用可变对象的属性,请确保该类的客户端无法获得对这些对象的引用。 切勿将这样的属性初始化为客户端提供的对象引用,或从访问方法返回属性。 在构造方法,访问方法和
readObject
方法(详见第 88 条)中进行防御性拷贝(详见第 50 条)。
示例代码:Item17Example01.java:代表了一个复数(包含实部和虚部的数字)。 除了标准的 Object
方法之外,它还为实部和虚部提供访问方法,并提供四个基本的算术运算:加法,减法,乘法和除法。 注意算术运算如何创建并返回一个新的 Complex
实例,而不是修改这个实例。 这种模式被称为函数式方法,因为方法返回将操作数应用于函数的结果,而不修改它们。
不可变对象很简单。 一个不可变的对象可以完全处于一种状态,也就是被创建时的状态。 如果确保所有的构造方法都建立了类不变量,那么就保证这些不变量在任何时候都保持不变,使用此类的程序员无需再做额外的工作。 另一方面,可变对象可以具有任意复杂的状态空间。 如果文档没有提供由设置(mutator)方法执行的状态转换的精确描述,那么可靠地使用可变类可能是困难的或不可能的。
不可变对象本质上是线程安全的; 它们不需要同步。 被多个线程同时访问它们时并不会被破坏。 这是实现线程安全的最简单方法。 由于没有线程可以观察到另一个线程对不可变对象的影响,所以不可变对象可以被自由地共享。 因此,不可变类应鼓励客户端尽可能重用现有的实例。 一个简单的方法是为常用的值提供公共的静态 final 常量。
public static final Complex ZERO = new Complex(0, 0);
public static final Complex ONE = new Complex(1, 0);
public static final Complex I = new Complex(0, 1);
不可变对象可以自由分享的结果是,你永远不需要做出防御性拷贝(defensive copies)(详见第 50 条)。 事实上,永远不需要做任何拷贝,因为这些拷贝永远等于原始对象。 因此,你不需要也不应该在一个不可变的类上提供一个 clone 方法或拷贝构造方法(copy constructor)(详见第 13 条)。 这一点在 Java 平台的早期阶段还不是很好理解,所以 String
类有一个拷贝构造方法,但是它应该尽量很少使用(详见第 6 条)。
不仅可以共享不可变的对象,而且可以共享内部信息。 例如,BigInteger
类在内部使用符号数值表示法。 符号用 int
值表示,数值用 int
数组表示。 negate
方法生成了一个数值相同但符号相反的新 BigInteger
实例。 即使它是可变的,也不需要复制数组;新创建的 BigInteger
指向与原始相同的内部数组。
不可变对象为其他对象提供了很好的构件(building blocks),无论是可变的还是不可变的。 如果知道一个复杂组件的内部对象不会发生改变,那么维护复杂对象的不变量就容易多了。这一原则的特例是,不可变对象可以构成 Map
对象的键和 Set
的元素,一旦不可变对象作为 Map
的键或 Set
里的元素,即使破坏了 Map
和 Set
的不可变性,但不用担心它们的值会发生变化。
不可变对象提供了免费的原子失败机制(详见第 76 条)。 它们的状态永远不会改变,所以不可能出现临时的不一致。
不可变类的主要缺点是对于每个不同的值都需要一个单独的对象。 创建这些对象可能代价很高,特别是如果是大型的对象下。
示例代码:Item17Example02.java:创建一个不可改变类,可以使其所有的构造方法私有或包级私有,并添加公共静态工厂,而不是公共构造方法。
如果一个类不能设计为不可变类,那么也要尽可能地限制它的可变性 。减少对象可以存在的状态数量,可以更容易地分析对象,以及降低出错的可能性。因此,除非有足够的理由把属性设置为非 final
的情况下,否则应该每个属性都设置为 final
的。
把本条目的建议与条目 15 的建议结合起来,你自然的倾向就是:除非有充分的理由不这样做,否则应该把每个属性声明为私有 final 的。