黑馬程序員——JavaSE之對單例枚舉和反射的看法一

                                          ------Java培訓、Android培訓、iOS培訓、.Net培訓、期待與您交流! -------



通常我們所使用的單例模式,我們都可以使用反射使它不再單例,如下餓漢式的單例模式:

public final class Singleton{
   private static final Singleton instance = new Singleton();
   private Singleton(){}
    
   public static Singleton getInstance(){
 	return instance;
   }
 
}
測試案例如下:
Singleton singleton1 = Singleton.getInstance();
Singleton singleton2 = Singleton.getInstance();

Constructor<Singleton> constructor = Singleton.class.getDeclaredConstructor();
constructor.setAccessible(true);
Singleton singleton3 = constructor.newInstance();

System.out.println(singleton1);
System.out.println(singleton2);
System.out.println(singleton3);
System.out.println(singleton1 == singleton2);
System.out.println(singleton2 == singleton3);

其中singleton1、singleton2都是通過我們所實現的單例模式來獲取的對象,他們應該是同一個對象,singleton3則是通過反射獲取無參構造器,constructor.setAccessible(true)來獲取訪問權限,最後通過無參構造器來創建一個對象singleton3,singleton3和上述兩者應該不是同一個對象,測試結果如下:



說通常我們所使用的單例模式,我們都可以使用反射使它不再單例。然而單例使用枚舉的話,卻可以避免被反射


public enum Singleton {
    instance{

        @Override
        protected void read() {
            System.out.println("read");
        }

        @Override
        protected void write() {
            System.out.println("write");
        }

    };
    protected abstract void read();
    protected abstract void write();
}

以上是一個單例枚舉的例子,而我們要獲取該實例只需要Singleton.INSTANCE,並且此種方式可以保證該單例線程安全、防反射攻擊、防止序列化生成新的實例。

枚舉單例關於防反射攻擊,當然和枚舉的實現有關,枚舉也是java類,我們對Singleton的class進行反編譯,可以得到一個新的類



public 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);
        }
  }
	//如果此類含有ENUM修飾,調用該方法時會直接報錯
	if((clazz.getModifiers() & Modifier.ENUM) != 0) 
		throw new IllegalArgumentException("Cannot reflectively create enum objects");
        ConstructorAccess ca = constructorAccessor;
        if(ca == null){
	    ca = acquireConstructorAccessor();	
	}
	return (T)ca.newInstance(initargs);
}

也就是說反射在通過newInstance創建對象時,會檢查該類是否是枚舉類,如果是,則拋出異常,反射失敗。 也就是說使用枚舉可以避免被反射,從而可以達到單例的效果。


可以發現以下特點:

  類的修飾abstract,所以沒法實例化,反射也無能爲力。
關於線程安全的保證,其實是通過類加載機制來保證的,我們看看INSTANCE的實例化時機,是在static塊中,JVM加載類的過程顯然是線程安全的。
對於防止反序列化生成新實例的問題還不是很明白,一般的方法我們會在該類中添加上如下方法,不過枚舉中也沒有顯示的寫明該方法。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章