第3條:用私有構造器或者枚舉類型強化Singleton屬性

  1:採用私有構造器來強化Singleton屬性工具

  顧名思義,即咱們須要定義一個private的構造器,但要注意一點,即便咱們定義了私有的構造器,可是客戶端仍是能夠藉助AccessibleObject.setAccessible方法,經過反射來調用私有的構造器,所以,咱們須要修改構造器來抵禦這種工具,下面代碼很好闡述了這個。測試

public class Singleton {
        private static final Singleton INSTANCE = new Singleton();

        private Singleton() {
            if (INSTANCE != null) {
                throw new UnsupportedOperationException("Instance already exist");
            }
        }

        public static Singleton getInstance() {
            return INSTANCE;
        }
}

  然而這樣作就能夠絕對防止出現多個實例了麼?其實如今還有一種狀況下會出現多個實例,那就是在你序列化這個對象以後,在進行反序列化,這個時候,你將再次獲得一個新的對象,讓咱們看下例子,首先實現序列化接口spa

public class Singleton implements Serializable{
                        ...
}

  接着咱們來看看序列化和反序列化後的狀況code

public class SerializableTest {

      //序列化
      private static  void serializable(Singleton singleton, String filename) throws IOException {
          FileOutputStream fos = new FileOutputStream(filename);
          ObjectOutputStream oos = new ObjectOutputStream(fos);
          oos.writeObject(singleton);
          oos.flush();
      }
      
      //反序列化
      @SuppressWarnings("unchecked")
      private static <T> T deserializable(String filename) throws IOException,
                                              ClassNotFoundException {
          FileInputStream fis = new FileInputStream(filename);
          ObjectInputStream ois = new ObjectInputStream(fis);
          return (T) ois.readObject();
      }
      
      public static void main(String[] args) throws IOException, ClassNotFoundException {
        //序列化到文件test.txt中
        serializable(Singleton.getInstance(),"F://test.txt");
        //反序列化
        Singleton singleton = deserializable("F://test.txt");
        
        //比較反序列化後獲得的singleton和Singleton.getInstance的地址是否同樣。
        System.out.println(singleton);
        System.out.println(Singleton.getInstance());
    }
}

  測試運行的結果以下圖所示,所以,咱們很明顯能夠看出獲得的是兩個不一樣的對象對象

  

  那麼如何解決序列化問題呢,其實很簡單,只要咱們在Singleton中加入下面方法便可,爲何只須要加入readResolve就行了呢,由於任何一個readObject方法,無論顯示仍是默認的,它都會返回一個新建的實例,這就是爲何上面兩個實例的地址是不同的緣由了,而加入了readResolve以後,那麼在反序列化以後,新建對象上的readResolve方法會被調用,而後該方法返回的對象引用將取代新建的對象,指向新建對象的引用就不會保留下來,當即成爲垃圾回收的對象了。blog

 private Object readResolve() {
        return INSTANCE;
 }

  

  

  2:利用枚舉來強化Singleton(最優方案)接口

  枚舉來強化方式很簡單,也不會出現上面這些狀況,利用單元素的枚舉來實現單例(Singleton),絕對防止屢次實例化,實現代碼以下:get

public enum Elvis{
    INSTANCE;
          
    private String[] favoriteSongs = {"Hound Dog","Heartbreak Hotl"};
    public void printFavorites(){
         System.out.println(Arrays.toString(favoriteSongs ));
    }      
}

  調用時只須要Elvis.INSTANCE.printFavorites();便可。it

相關文章
相關標籤/搜索