第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;
        }
}

  然而这样做就可以绝对防止出现多个实例了么?其实现在还有一种情况下会出现多个实例,那就是在你序列化这个对象之后,在进行反序列化,这个时候,你将再次得到一个新的对象,让我们看下例子,首先实现序列化接口

public class Singleton implements Serializable{
                        ...
}

  接着我们来看看序列化和反序列化后的情况

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方法会被调用,然后该方法返回的对象引用将取代新建的对象,指向新建对象的引用就不会保留下来,立即成为垃圾回收的对象了。

 private Object readResolve() {
        return INSTANCE;
 }

  

  

  2:利用枚举来强化Singleton(最优方案)

  枚举来强化方式很简单,也不会出现上面这些情况,利用单元素的枚举来实现单例(Singleton),绝对防止多次实例化,实现代码如下:

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

  调用时只需要Elvis.INSTANCE.printFavorites();即可。

作者:哀&RT
出处:博客园哀&RT的技术博客--http://www.cnblogs.com/Tony-Anne/
您的支持是对博主最大的鼓励,感谢您的认真阅读。
本文版权归作者所有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
原文地址:https://www.cnblogs.com/Tony-Anne/p/6694277.html