那么現在再寫一種最受歡迎的單例模式,即枚舉單例模式。
枚舉模式的代碼如下:
date是為了測試方便。
public enum EnumInstance { INSTANCE; private Object date; public Object getDate() { return date; } public void setDate(Object date) { this.date = date; } public static EnumInstance getInstance(){ return INSTANCE; } }
1、那么我們可以寫一個序列化的栗子進行測試。
public class SerivalTest { public static void main(String[] args) throws IOException, ClassNotFoundException { EnumInstance instance = EnumInstance.getInstance(); instance.setDate(new Object()); //放文件 ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("single_file")); outputStream.writeObject(instance); //取文件 File file = new File("single_file"); ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream(file)); EnumInstance hungrySingleton = (EnumInstance) inputStream.readObject(); System.out.println(instance); System.out.println(hungrySingleton); System.out.println(instance==hungrySingleton); } }
結果為:
怎么樣?枚舉就是這么強大,那么序列化和反序列化對枚舉是怎么處理的呢?首先通過inputStream.readObject()進入,找到readEnum()方法。
如下:
是通過類型和name進行獲得枚舉常量,因為枚舉中的name是唯一的,並且對應一個枚舉常量,所以2012行拿到的肯定是唯一的常量對象,
這樣呢就沒有創建新的對象。維持了這個對象的單例屬性。枚舉中這個處理方法還是很簡單的,而且很容易理解,所以枚舉類對於序列化
這個破壞是不受影響的。
2、寫一個反射攻擊的例子。
public class Testreflection { public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { Class object = EnumInstance.class; Constructor constructor = object.getDeclaredConstructor(); constructor.setAccessible(true); EnumInstance instance = EnumInstance.getInstance(); EnumInstance newInstance = (EnumInstance) constructor.newInstance(); System.out.println(instance.getDate()); System.out.println(newInstance.getDate()); System.out.println(instance.getDate() == newInstance.getDate()); } }
看結果:
表示沒有獲得無參構造器,那么我們打開源碼看一哈。java.lang.enum 可以看到枚舉沒有無參構造器,而且僅有一個傳兩個參數的構造器,如下所示。那么我們就將這兩個參數傳進去,再測試。
可以看到又拋了額一個異常,但是可以很清楚的了解到這個異常是說不能反射去創建對象,我們從報錯的地方進入源碼查看詳情。
源碼清清楚楚的告訴我們如果是枚舉類型,就拋出異常,可見枚舉是多么的強大!