單例,列舉,反射,序列化--effectiveJava讀書筆記
阿新 • • 發佈:2019-01-30
先看一個單例:
public class Singleton{
private final static Singleton INSTANCE = new Singleton();
private Singleton(){};
public static Singleton getInstance(){return INSTANCE;}
}
我們用序列化來打破單例
public class Singleton implements Serializable{ private final static Singleton INSTANCE = new Singleton(); private Singleton(){}; public static Singleton getInstance(){return INSTANCE;} public static void main(String[] args) throws FileNotFoundException, IOException, ClassNotFoundException { Singleton s1 = Singleton.getInstance(); File objectF = new File("/object"); ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(objectF)); out.writeObject(s1); out.close(); ObjectInputStream in = new ObjectInputStream(new FileInputStream(objectF)); Singleton s2 = (Singleton) in.readObject(); in.close(); System.out.println("是單例麼?" + (s1 == s2)); } }
將會列印:
是單例麼?false。
可見我們可以這樣破壞其單例屬性。要保持應該怎麼辦呢?需要增加readResolve方法,Java反序列化的時候會用這個方法的返回值直接代替序列化得到的物件
列印:public class Singleton implements Serializable{ private final static Singleton INSTANCE = new Singleton(); private Singleton(){}; public static Singleton getInstance(){return INSTANCE;} private Object readResolve() { return INSTANCE; } public static void main(String[] args) throws FileNotFoundException, IOException, ClassNotFoundException { Singleton s1 = Singleton.getInstance(); File objectF = new File("/object"); ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(objectF)); out.writeObject(s1); out.close(); ObjectInputStream in = new ObjectInputStream(new FileInputStream(objectF)); Singleton s2 = (Singleton) in.readObject(); in.close(); System.out.println("是單例麼?" + (s1 == s2)); } }
是單例麼?true
我們再通過反射來打破其的單例性:
public class Singleton{ private final static Singleton INSTANCE = new Singleton(); private Singleton(){}; public static Singleton getInstance(){return INSTANCE;} public static void main(String[] args) throws FileNotFoundException, IOException, ClassNotFoundException, SecurityException, NoSuchMethodException, IllegalArgumentException, InstantiationException, IllegalAccessException, InvocationTargetException { Singleton s1 = Singleton.getInstance(); Constructor<Singleton> c = Singleton.class.getDeclaredConstructor(); c.setAccessible(true); Singleton s2 = c.newInstance(); System.out.println("是單例麼?" + (s1 == s2)); } }
將會列印:
是單例麼?false。
說明使用反射呼叫私有構造器也是可以破壞單例的,解決的辦法是如下:public class Singleton{
private final static Singleton INSTANCE = new Singleton();
private Singleton(){
if(++COUNT > 1){
throw new RuntimeException("can not be construt more than once");
}
};
private static int COUNT = 0;
public static Singleton getInstance(){
return INSTANCE;
}
}
這樣當使用反射呼叫的時候,就會丟擲異常。
再用clone來破壞單例性
public class Singleton implements Cloneable{
private final static Singleton INSTANCE = new Singleton();
public static Singleton getInstance(){
return INSTANCE;
}
public static void main(String[] args) {
Singleton s1 = Singleton.getInstance();
Singleton s2 = (Singleton) s1.clone();
System.out.println("是單例麼?" + (s1 == s2));
}
}
這樣也會發現不是單例了,辦法是重新clone方法。
public class Singleton implements Cloneable{
private final static Singleton INSTANCE = new Singleton();
public static Singleton getInstance(){
return INSTANCE;
}
@Override
protected Object clone() throws CloneNotSupportedException {
return INSTANCE;
}
public static void main(String[] args) {
Singleton s1 = Singleton.getInstance();
Singleton s2 = (Singleton) s1.clone();
System.out.println("是單例麼?" + (s1 == s2));
}
}
如果想要比較簡便的避免上訴的問題,最好的方式是使用列舉:
public enum SingleEnum {
INSTANCE;
public static SingleEnum getInstance(){
return INSTANCE;
}
}
其通過反射會丟擲如下異常:
java.lang.NoSuchMethodException: com.price.effective.create.SingleEnum.<init>()
通過反序列化其也會返回INSTANCE物件。
其沒有clone方法
綜上,Enum可以作為想用單例時的第一選擇。