effective java 第三版 条目3 使用私有构造器或者
单例是一个类只能被实例化一次,典型的单例的表现是一个无状态(没有字段)的对象。如一个函数或者一个从本质上将是唯一的系统组件。让一个类单例将会使这测试它的客户端变得困难( Making a class a singleton can make it difficult to test its clients)。因为要代替单例一个虚假的实现是很困难的。除非他实现了一个为他的类型服务的接口。
这里有两个普通的方式来实现单例。两个都是建立在私有化构造器然后暴露一个共有静态成员来提供一个单独实例的入口,在第一种方式中,这个静态成员是一个字段:
// Singleton with public final field
public class Elvis {
public static final Elvis INSTANCE = new Elvis();
private Elvis() { }
public void leaveTheBuilding() { }
}
这个私有的构造器只会被调用一次,来实例化这个共有静态常量字段Elvis.INSTANCE。没有公有和保护的构造器会保证类的独占,Elvis的实例化将只会存在一个——不会更多,也不会更少。客户端不可能改变这个事实。这里有一个警告:一个特权是客户端可以使用反射机制来创建一个另外的实例。如果你需要定义一个方式来反对这个方式,你可以修改构造器来让第二次构造会抛出一个异常!(比如增加一个静态字段count来计算构造实例的数量)。
第二种方式来实现一个单例是,提供一个静态工厂方法、
// Singleton with static factory
public class Elvis {
private static final Elvis INSTANCE = new Elvis(); private Elvis() { ... }
public static Elvis getInstance() { return INSTANCE; }
public void leaveTheBuilding() { ... }
}
所有这些Elvis.getInstance将会返回一个相同的对象引用,同时不会有另一个实例将会再被创建
公共字段的方式最大的好处是这个API使这个类是单例非常清晰,这个公共静态字段时final的,所以它将总是包含一个相同的引用,第二好处是它将会更简单。
公共静态方法的一个好处是,它提供一个灵活的方式去不需要改变API就可以改变是否这个类是单例的(比如你不想让它单例了?或者将会有多个实例而不是一个?)。这个工厂方法将会返回一个单独的实例,但是他可能被被修改,返回为每一个线程返回一个实例!第二个好处是,如果你的应用需要,你可写一个类的单例工厂(条目30)。最后一个好处是,使用静态工厂方法,方法引用将可以被提供使用!例如
Elvis::instance是一个Supplier<Elivs>。
除非静态工厂中的某一个好处是很重要的,否则公有字段的方式将会使更好的。
为了使一个单例类可以使用它们的方式序列化(条目12),仅仅是给类去添加实现一个Serializable 接口不是很好。为了保持这个单例保证,申明所有的实例字段transient 再提供readResolve 方法(条目89)。如果不这样做,每一次一个序列化实例被反序列化,一个新的实例将会被创建,最重要的是,这破坏类我们单例的要求。为了伪造 Elvis sightings,为了防止这样的情况发生,添加一个addResolve方法到Elvis 类。
// readResolve method to preserve singleton property
private Object readResolve() {
// Return the one true Elvis and let the garbage collector
// take care of the Elvis impersonator.
return INSTANCE;
}
第三种方式实现单例模式就是去声明一个single-element enum
// Enum singleton - the preferred approach
public enum Elvis {
INSTANCE;
public void leaveTheBuilding() { ... }
}
这个方式和公共字段方式很相似,但是这个方式更简单明了,同时它会免费提供一个序列化机制,同时提供一个更严格的保证对于防止出现多重的实例。甚至在应对复杂的序列化和反射机制!这个方式可能让人感觉有一点不自然,但是一个single-element enum通常是最好的方式去实现一个单例,记住你不能使用这个方式如果你的单例必须继承某个类而不是Enum(尽管你可以声明这个枚举去实现一个接口)