日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

为什么要用枚举实现单例模式(避免反射、序列化问题)

發布時間:2025/3/12 编程问答 25 豆豆
生活随笔 收集整理的這篇文章主要介紹了 为什么要用枚举实现单例模式(避免反射、序列化问题) 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

1 引言

? 相信如果能看到我這篇博客的小伙伴,肯定都看過Joshua Bloch大神說過的這句話:“單元素的枚舉類型已經成為實現Singleton的最佳方法”。其實,第一次讀到這句話,我連其中說的單元素指什么都不知道,尷尬。后來,網上看了搜索了好幾篇文章,發現基本上都是轉載自相同的一篇文章,而我的困惑是為什么要用枚舉類型實現單例模式呢”,文章中都說的很籠統,于是決定自己結合Joshua Bloch的《effective java》寫一篇總結下,給后來的同學做個參考。

2 什么是單例模式

? 關于什么是單例模式的定義,我之前的文章中講過,主要是講惡漢懶漢、線程安全方面得問題,我就不再重復了,只是做下單例模式的總結。之前文章中實現單例模式三個主要特點:1、構造方法私有化;2、實例化的變量引用私有化;3、獲取實例的方法共有。

? 如果不使用枚舉,大家采用的一般都是“雙重檢查加鎖”這種方式,如下,對單例模式還不了解的同學希望先大致看下這種思路,接下來的3.1和3.2都是針對這種實現方式進行探討,了解過單例模式的同學可以跳過直接看3.1的內容:

public class Singleton {private volatile static Singleton uniqueInstance;private Singleton() {}public static Singleton getInstance() {if (uniqueInstance == null) {synchronized (Singleton.class){if(uniqueInstance == null){//進入區域后,再檢查一次,如果仍是null,才創建實例uniqueInstance = new Singleton();}}}return uniqueInstance;} }

3 為什么要用枚舉單例

3.1 私有化構造器并不保險

? 《effective java》中只簡單的提了幾句話:“享有特權的客戶端可以借助AccessibleObject.setAccessible方法,通過反射機制調用私有構造器。如果需要低于這種攻擊,可以修改構造器,讓它在被要求創建第二個實例的時候拋出異常。”下面我以代碼來演示一下,大家就能明白:

public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {Singleton s=Singleton.getInstance();Singleton sUsual=Singleton.getInstance();Constructor<Singleton> constructor=Singleton.class.getDeclaredConstructor();constructor.setAccessible(true);Singleton sReflection=constructor.newInstance();System.out.println(s+"\n"+sUsual+"\n"+sReflection);System.out.println("正常情況下,實例化兩個實例是否相同:"+(s==sUsual));System.out.println("通過反射攻擊單例模式情況下,實例化兩個實例是否相同:"+(s==sReflection));}

輸出為:

com.lxp.pattern.singleton.Singleton@1540e19d com.lxp.pattern.singleton.Singleton@1540e19d com.lxp.pattern.singleton.Singleton@677327b6 正常情況下,實例化兩個實例是否相同:true 通過反射攻擊單例模式情況下,實例化兩個實例是否相同:false

既然存在反射可以攻擊的問題,就需要按照Joshua Bloch做說的,加個異常處理。這里我就不演示了,等會講到枚舉我再演示。

3.2 序列化問題

大家先看下面這個代碼:

public class SerSingleton implements Serializable {private volatile static SerSingleton uniqueInstance;private String content;public String getContent() {return content;}public void setContent(String content) {this.content = content;}private SerSingleton() {}public static SerSingleton getInstance() {if (uniqueInstance == null) {synchronized (SerSingleton.class) {if (uniqueInstance == null) {uniqueInstance = new SerSingleton();}}}return uniqueInstance;}public static void main(String[] args) throws IOException, ClassNotFoundException {SerSingleton s = SerSingleton.getInstance();s.setContent("單例序列化");System.out.println("序列化前讀取其中的內容:"+s.getContent());ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("SerSingleton.obj"));oos.writeObject(s);oos.flush();oos.close();FileInputStream fis = new FileInputStream("SerSingleton.obj");ObjectInputStream ois = new ObjectInputStream(fis);SerSingleton s1 = (SerSingleton)ois.readObject();ois.close();System.out.println(s+"\n"+s1);System.out.println("序列化后讀取其中的內容:"+s1.getContent());System.out.println("序列化前后兩個是否同一個:"+(s==s1));}}

先猜猜看輸出結果:

序列化前讀取其中的內容:單例序列化 com.lxp.pattern.singleton.SerSingleton@135fbaa4 com.lxp.pattern.singleton.SerSingleton@58372a00 序列化后讀取其中的內容:單例序列化 序列化前后兩個是否同一個:false

? 可以看出,序列化前后兩個對象并不想等。為什么會出現這種問題呢?這個講起來,又可以寫一篇博客了,簡單來說“任何一個readObject方法,不管是顯式的還是默認的,它都會返回一個新建的實例,這個新建的實例不同于該類初始化時創建的實例。”當然,這個問題也是可以解決的,想詳細了解的同學可以翻看《effective java》第77條:對于實例控制,枚舉類型優于readResolve。

3.3 枚舉類詳解

3.3.1 枚舉單例定義

咱們先來看一下枚舉類型單例:

public enum EnumSingleton {INSTANCE;public EnumSingleton getInstance(){return INSTANCE;} }

怎么樣,是不是覺得好簡單,只有這么點代碼,其實也沒這么簡單啦,編譯后相當于:

public final class EnumSingleton extends Enum< EnumSingleton> {public static final EnumSingleton ENUMSINGLETON;public static EnumSingleton[] values();public static EnumSingleton valueOf(String s);static {}; }

咱們先來驗證下會不會避免上述的兩個問題,先看下枚舉單例的優點,然后再來講原理。

3.3.2 避免反射攻擊

public enum EnumSingleton {INSTANCE;public EnumSingleton getInstance(){return INSTANCE;}public static void main(String[] args) throws IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchMethodException {EnumSingleton singleton1=EnumSingleton.INSTANCE;EnumSingleton singleton2=EnumSingleton.INSTANCE;System.out.println("正常情況下,實例化兩個實例是否相同:"+(singleton1==singleton2));Constructor<EnumSingleton> constructor= null;constructor = EnumSingleton.class.getDeclaredConstructor();constructor.setAccessible(true);EnumSingleton singleton3= null;singleton3 = constructor.newInstance();System.out.println(singleton1+"\n"+singleton2+"\n"+singleton3);System.out.println("通過反射攻擊單例模式情況下,實例化兩個實例是否相同:"+(singleton1==singleton3));} }

結果就報異常了:

Exception in thread "main" java.lang.NoSuchMethodException: com.lxp.pattern.singleton.EnumSingleton.<init>()at java.lang.Class.getConstructor0(Class.java:3082)at java.lang.Class.getDeclaredConstructor(Class.java:2178)at com.lxp.pattern.singleton.EnumSingleton.main(EnumSingleton.java:20)at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)at java.lang.reflect.Method.invoke(Method.java:498)at com.intellij.rt.execution.application.AppMain.main(AppMain.java:144) 正常情況下,實例化兩個實例是否相同:true

然后debug模式,可以發現是因為EnumSingleton.class.getDeclaredConstructors()獲取所有構造器,會發現并沒有我們所設置的無參構造器,只有一個參數為(String.class,int.class)構造器,然后看下Enum源碼就明白,這兩個參數是name和ordial兩個屬性:

public abstract class Enum<E extends Enum<E>>implements Comparable<E>, Serializable {private final String name;public final String name() {return name;}private final int ordinal;public final int ordinal() {return ordinal;}protected Enum(String name, int ordinal) {this.name = name;this.ordinal = ordinal;}//余下省略

? 枚舉Enum是個抽象類,其實一旦一個類聲明為枚舉,實際上就是繼承了Enum,所以會有(String.class,int.class)的構造器。既然是可以獲取到父類Enum的構造器,那你也許會說剛才我的反射是因為自身的類沒有無參構造方法才導致的異常,并不能說單例枚舉避免了反射攻擊。好的,那我們就使用父類Enum的構造器,看看是什么情況:

public enum EnumSingleton {INSTANCE;public EnumSingleton getInstance(){return INSTANCE;}public static void main(String[] args) throws IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchMethodException {EnumSingleton singleton1=EnumSingleton.INSTANCE;EnumSingleton singleton2=EnumSingleton.INSTANCE;System.out.println("正常情況下,實例化兩個實例是否相同:"+(singleton1==singleton2));Constructor<EnumSingleton> constructor= null; // constructor = EnumSingleton.class.getDeclaredConstructor();constructor = EnumSingleton.class.getDeclaredConstructor(String.class,int.class);//其父類的構造器constructor.setAccessible(true);EnumSingleton singleton3= null;//singleton3 = constructor.newInstance();singleton3 = constructor.newInstance("testInstance",66);System.out.println(singleton1+"\n"+singleton2+"\n"+singleton3);System.out.println("通過反射攻擊單例模式情況下,實例化兩個實例是否相同:"+(singleton1==singleton3));} }

然后咱們看運行結果:

正常情況下,實例化兩個實例是否相同:true Exception in thread "main" java.lang.IllegalArgumentException: Cannot reflectively create enum objectsat java.lang.reflect.Constructor.newInstance(Constructor.java:417)at com.lxp.pattern.singleton.EnumSingleton.main(EnumSingleton.java:25)at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)at java.lang.reflect.Method.invoke(Method.java:498)at com.intellij.rt.execution.application.AppMain.main(AppMain.java:144)

? 繼續報異常。之前是因為沒有無參構造器,這次拿到了父類的構造器了,只是在執行第17行(我沒有復制import等包,所以行號少于我自己運行的代碼)時候拋出異常,說是不能夠反射,我們看下Constructor類的newInstance方法源碼:

@CallerSensitivepublic T newInstance(Object ... initargs)throws InstantiationException, IllegalAccessException,IllegalArgumentException, InvocationTargetException{if (!override) {if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {Class<?> caller = Reflection.getCallerClass();checkAccess(caller, clazz, null, modifiers);}}if ((clazz.getModifiers() & Modifier.ENUM) != 0)throw new IllegalArgumentException("Cannot reflectively create enum objects");ConstructorAccessor ca = constructorAccessor; // read volatileif (ca == null) {ca = acquireConstructorAccessor();}@SuppressWarnings("unchecked")T inst = (T) ca.newInstance(initargs);return inst;}

請看第12行源碼,說明反射在通過newInstance創建對象時,會檢查該類是否ENUM修飾,如果是則拋出異常,反射失敗。

3.3.3 避免序列化問題

我按照3.2中方式來寫,作為對比,方面大家看的更清晰些:

public enum SerEnumSingleton implements Serializable {INSTANCE;private String content;public String getContent() {return content;}public void setContent(String content) {this.content = content;}private SerEnumSingleton() {}public static void main(String[] args) throws IOException, ClassNotFoundException {SerEnumSingleton s = SerEnumSingleton.INSTANCE;s.setContent("枚舉單例序列化");System.out.println("枚舉序列化前讀取其中的內容:"+s.getContent());ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("SerEnumSingleton.obj"));oos.writeObject(s);oos.flush();oos.close();FileInputStream fis = new FileInputStream("SerEnumSingleton.obj");ObjectInputStream ois = new ObjectInputStream(fis);SerEnumSingleton s1 = (SerEnumSingleton)ois.readObject();ois.close();System.out.println(s+"\n"+s1);System.out.println("枚舉序列化后讀取其中的內容:"+s1.getContent());System.out.println("枚舉序列化前后兩個是否同一個:"+(s==s1));} }

運行結果如下:

1 枚舉序列化前讀取其中的內容:枚舉單例序列化 2 INSTANCE 3 INSTANCE 4 枚舉序列化后讀取其中的內容:枚舉單例序列化 5 枚舉序列化前后兩個是否同一個:true

? 枚舉類是JDK1.5才出現的,那之前的程序員面對反射攻擊和序列化問題是怎么解決的呢?其實就是像Enum源碼那樣解決的,只是現在可以用enum可以使我們代碼量變的極其簡潔了。至此,相信同學們應該能明白了為什么Joshua Bloch說的“單元素的枚舉類型已經成為實現Singleton的最佳方法”了吧,也算解決了我自己的困惑。既然能解決這些問題,還能使代碼量變的極其簡潔,那我們就有理由選枚舉單例模式了。對了,解決序列化問題,要先懂transient和readObject,鑒于我的主要目的不在于此,就不在此寫這兩個原理了。

Java transient關鍵字使用可以參考這個:https://www.cnblogs.com/lanxuezaipiao/p/3369962.html

參考:

1、《Effective Java》(第2版):p14-15,p271-274

總結

以上是生活随笔為你收集整理的为什么要用枚举实现单例模式(避免反射、序列化问题)的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。

主站蜘蛛池模板: 久久久久黄 | 欧美交换配乱吟粗大25p | 午夜视频在线免费看 | 少妇太爽了 | 欧美视频一区在线观看 | 免费精品一区 | 中国一及毛片 | 日韩三级在线观看 | 999久久久精品视频 亚洲视频精品在线 | 天天射夜夜撸 | 九九免费视频 | 日日久| 国产精品一区二区三 | 91影音| 奇米久久久 | 视频一区二区中文字幕 | 一级一片免费播放 | 亚洲成人aaa | 精品97人妻无码中文永久在线 | 少妇搡bbbb搡bbb搡打电话 | 免费在线观看黄色网址 | 狼人综合视频 | 免费在线视频观看 | 99re在线播放| 人妖交videohd另类 | 久久久久久久久久久久97 | 色欲av无码精品一区 | 亚洲人在线视频 | 四虎视频国产精品免费 | 色婷婷色丁香 | 久久免费视频6 | 青青草日韩 | 免费毛片大全 | 国久久| 国产精品爽 | 少妇视频在线观看 | 国产精品久久久久久一区二区 | 台湾佬中文字幕 | 国产欧美日韩一区 | 仙踪林久久久久久久999 | 先锋资源网av | 久久加久久 | 日韩一区二区中文字幕 | 欧美大片视频在线观看 | 国产精品成人免费一区久久羞羞 | 成人av网站大全 | 精品国产一区二区三区久久久蜜月 | 日韩精品在线观看一区二区三区 | 777米奇影视第四色 五月丁香久久婷婷 | 天堂√| 久久精品国产77777蜜臀 | 少妇影院在线观看 | 亚洲一区欧洲二区 | 老司机狠狠爱 | 日本涩涩网站 | 亚洲精品一区在线观看 | 国产精品成人aaaaa网站 | 久久国产乱子伦免费精品 | 日韩精品国产一区 | 2024av视频 | 亚洲AV午夜成人片 | 国产区网址 | 国产真人做爰毛片视频直播 | 欧美www视频| 亚洲天堂三区 | 免费黄色看片 | 天天草天天操 | 欧美日韩卡一卡二 | 亚洲天堂va | 蜜臀视频在线观看 | 国产在线国偷精品免费看 | 视频一区欧美 | 三级在线观看网站 | 天堂а√在线中文在线 | 丝袜国产在线 | 中文字幕精品在线观看 | 人人人人爽 | 91porny在线| 国产精品视频一区二区在线观看 | 日本欧美色 | 91网视频 | 国产精品国产三级国产传播 | 精品国产午夜 | 特级西西444www高清大胆 | 人人爽爽人人 | 国产福利小视频在线观看 | 久久亚洲精精品中文字幕早川悠里 | 亚洲黄色免费网站 | 1级黄色大片儿 | 久久激情免费视频 | 日本午夜在线 | 亚洲少妇毛片 | 男人天堂视频网站 | 91大神视频在线播放 | 男人的天堂色 | 久久久免费在线观看 | 日本一区二区在线不卡 | 国产在线拍揄自揄拍无码 | 国产高清视频 |