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();即可。