- 采用私有构造器来强化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;
}
- 利用枚举来强化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();即可。