单例只能被类实例化一次。单例例要么表示一个⽆状态的对象,要么表示一个本质上独⼀无二的系统组件。单例会使测试它的客户端很难,因为它不可能去模拟单例除非它实现了了作为其类型的接口。
有两种方式实现单例。两种方式都是基于私有构造方法和公共静态成员来提供基础实例。一种方式,成员为final
1 | // Singleton with public final field |
私有构造方法只被调用一次,用于初始化公共静态最终变量Elvis.INSTANCE。public或者protected构造方法缺失因此可以保证唯一性,Elvis实例话事,只有唯一一个Elvis实例会存在。客户端是无法修改的:通过设置AccessibleObject.setAccessible 方法,执行私有构造方法。如果你需要防御不利的攻击,修改构造器,如果它创建第二个实例的时候抛出异常。
第二种方法,公共成员变成静态工厂方法。
1 | // Singleton with static factory |
调用Elvis.getInstance放回相同的对象引用,不会再创建其他的Elvis实例。
public 属性方法的主要优点是类的单例API清晰,public static属性是final,所以他总是包含相同的对象引用。第二个优点是简单。
静态工厂方法的一个优点是给定了一定的灵活性去改变获取单例的api。工厂方法返回唯一的单例,不过你可以修改这个方法,实例在每个线程单中是独立的。第二个优点是如果应用程序需要它,你可以写一个通用的单例工厂。使用静态工厂的最后优点是可以使用方法引用,例如Elvis::instance 。除非这些优点都是有意义的,否则公共属性方法更可取。
想让上述两种⽅式实现的单例例类能够序列化,仅仅在类的声明中添加 implements Serializable是不够的。为了确保单例,请将所有实例字段声明为transient,并 提供一个readResolve方法。否则,每次当序列化的实例例在反序列化时,一个新的实例就会被创建出来,对于我们这个示例来说,就会出现另一个Elvis。为了了防⽌止这种情况发⽣,请将如下的readResolve⽅方法添加到Elvis类中:
1 | // readResolve method to preserve singleton property |
实现单例的第三种⽅式是声明⼀一个单元素的枚举:
1 | // Enum singleton - the preferred approach |
该⽅式类似于公有字段⽅式,不过更加简洁,而且天然提供了序列化机制,能够强有力地保证不会出现多次实例化的情况,即便在复杂的序列化与反射场景下亦如此。该⽅式看起来有些不那么⾃然,不过单元素枚举类型通常是实现单例的最佳⽅方式。注意,如果单例必须要继承一个父类而⾮非枚举的情况下是无法使用该⽅式的(不过可以声明一个实现了接口的枚举)