Singleton只不過是指僅僅實例化一次的類。Singleton通常被用來代表那些本質上唯一的系統組件,比如窗口管理器或者文件系統。使類成為Singleton會使它的客戶端測試變得十分困難,因為無法給Singleton替換模擬實現,除非它實現一個充當其類型的接口。
單例的實現
在[Java]1.5發行版本之前,實現Singleton有兩種方法。這兩種方法都要把構造器保持為私有的,并導出公有的靜態成員,以便允許客戶端能夠訪問該類的唯一實例。
第一種:公有靜態final成員
public class Singleton {
public static final Singleton INSTANCE = new Singleton();
private Singleton() {
// 初始化操作
}
public void execute() {
System.out.println("execute Singleton");
}
}
但是,這種寫法可以通過反射機制調用私有的構造器。
Class singletonClass = Class.forName("effactive.java.eff003.Singleton");
Constructor constructor = singletonClass.getDeclaredConstructor();
constructor.setAccessible(Boolean.TRUE);
Singleton singleton = (Singleton) constructor.newInstance();
singleton.execute();
為了避免反射機制調用私有的構造器需要在修改私有的構造器,當試圖創建第二個實例是拋出異常。
private Singleton() {
if (INSTANCE != null){
throw new IllegalStateException("Already instantiated");
}
}
這樣,在創建第二個實例是就會拋出異常。保證始終只有一個實例。
第二種:公有的靜態工廠方法
public class Singleton2 {
private static final Singleton2 INSTANCE = new Singleton2();
private Singleton2() {
if (INSTANCE != null){
throw new IllegalStateException("Already instantiated");
}
}
public static Singleton2 getInstance(){
return INSTANCE;
}
public void execute() {
System.out.println("execute Singleton2");
}
}
靜態工廠方法要比第一種公有靜態final成員靈活一些??梢栽诓桓淖傾PI的前提下,改變該類是否是單例的想法。但是,這種寫法仍可以通過反射機制調用私有的構造器。
在Java 1.5之后我們有第三種。
第三種:單個元素的枚舉類型
public enum Singleton3 {
INSTANCE;
public void execute() {
System.out.println("execute Singleton3");
}
}
使用的話:
Singleton3.INSTANCE.execute();
由于Java的枚舉類型實現了Serializable接口,默認是可以序列化的,而且還能包證反序列化之后不會重新創建一個實例。
單例的序列化
如果我們將單例序列化,那么當我們反序列化,還會單例嗎?
對于第一種和第二種來說,反序列化之后,我們相當與重新創建了一個新的實例。不能再保證單例了。
對于第三種,由于JAVA在枚舉類型反序列化時候與一般類的不一樣,可以保證反序列化之后的依然是單例。
下面我們來解決第一種和第二種反序列化的問題。
public class Singleton4 implements Serializable{
private static final Singleton4 INSTANCE = new Singleton4();
private Singleton4() {
if (INSTANCE != null){
throw new IllegalStateException("Already instantiated");
}
}
public static Singleton4 getInstance(){
return INSTANCE;
}
//需要該方法來保證反序列化后仍為同一對象
private Object readResolve() {
return Singleton4.INSTANCE;
}
public void execute() {
System.out.println("execute Singleton4");
}
}
下面是測試的代碼
File file = new File("/home/pj/person.out");
ObjectOutputStream oout = new ObjectOutputStream(new FileOutputStream(file));
oout.writeObject(Singleton4.getInstance());
oout.close();
ObjectInputStream oin = new ObjectInputStream(new FileInputStream(file));
Singleton4 singleton4 = (Singleton4) oin.readObject();
oin.close();
System.out.println(Singleton4.getInstance());
System.out.println(singleton4);
System.out.println(Singleton4.getInstance() == singleton4);
總結:
對比來看,單元素的枚舉類型應該是實現單例的最佳方式了。