1. 程式人生 > >單例,列舉,反射,序列化--effectiveJava讀書筆記

單例,列舉,反射,序列化--effectiveJava讀書筆記

先看一個單例:

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可以作為想用單例時的第一選擇。