单例(singleton)仅仅是一个恰好能实例化一次的类[Gamma95]。单例一般代表一个无状态的对象(比如,一个函数),或者一个本质唯一性的系统组件。一个类变为单例使得难于测试它的客户端,因为替代单例的模拟(mock)实现是不可能的,除非它实现了一个作为自己类型的接口。
有两种通常的方式实现单例。两者都是基于保持构造子私有和导出提供获取这个单一实例的公开静态成员。在第一个方法中,成员是一个final域:
// 公开final域的单例
public class Elvis {
public static final Elvis INSTANCE = new Elvis();
private Elvis() { ... }
public void leaveTheBuilding() { ... }
}
为了初始化公开静态final域Elvis.INSTANCE,私有构造子只被调用一次。公开或者受保护的构造子的缺失可以保证一个“单一Elvis(monoelvistic)”的世界:一旦Elivs类被初始化,仅仅有一个Elivs类,不会多也不会少。客户端不能够改变这个现实,但是有一个警告:一个有特权的客户端可以用AccessibleObject.setAccessible方法反射调用私有构造子(条目65)。如果你需要防止这个攻击,需要更改构造子,使得当要求创建第二个对象时抛出异常。
实现单例的第二个方法是,一个公开的方法是一个静态工厂方法:
// 静态工厂的单例
public class Elvis {
private static final Elvis INSTANCE = new Elvis();
public static Elvis getInstance() { return INSTANCE; }
public void leaveTheBuilding() { ... }
}
所有调用Elvis.getInstance都返回同一个对象的引用,没有其他的Elvis实例创建(就像上面提到的警告一样)。
公开域方法的主要优点是,API清楚表明这个类是单例:公开静态域是final的,所有它总是含有同一个对象引用。第二个优点是它更加简洁。
静态工厂方法的一个优点是,你可以灵活的改变这个类是否是单例,而不改变它的API。工厂方法返回单一实例,但是它可以修改成返回,比如说,每个线程调用的各自的实例。第二个优点是,如果你的应用需要时,可以编写泛型单例工厂(generic singleton factory)(条目30)。用静态工厂的最后一个优点是,方法引用可以被用作做supplier,比如,Elvis::instance是一个Supplier<Elivs>。除非这些优点有一个是相关的,否则公开域方法是更可取的。
为了使用这些方法之一的单例类是可系列化的(serializable),仅仅添加implements Serializable到这个声明是不充分的。为了保证单例,声明所有实例域为transient,而且提供readResolve方法(条目89)。否则,一个序列化的实例反系列化时,一个新的实例会创建,对于这个例子来说,导致Elvis的幻例。为了防止这个发生,添加readResolve方法到Elivs类:
// 保存单例属性的readResolve方法
private Object readResolve() {
// Return the one true Elvis and let the garbage collector
// take care of the Elvis impersonator.
return INSTANCE;
}
实现单例的第三个方式是声明单个元素的枚举:
// Enum单例 - 更好的方法
public enum Elvis {
INSTANCE;
public void leaveTheBuilding() { ... }
}
这个方法类似于公开域方法,但是更精确的说,免费提供了系列化机制,同时对防御多个实例提供了有力的保证,即使面对系列化和反射攻击的复杂情况。这个方法感觉有点不自然,但是单个元素枚举类型通常是实现单例的最好方式。记住,如果你的单例必须扩展一个超类而不是Enum时(尽管你可以声明一个枚举来实现接口),就不能够使用这个方法。