在1.5版本之前可以两种实现Singleton的方法,但是都要把构造器保存为私有的。
1、公有静态成员是个final域
- // Singleton with public final field - Page 17
- public class Elvis {
- public static final Elvis INSTANCE = new Elvis();
- private Elvis() { }
- public void leaveTheBuilding() {
- System.out.println("Whoa baby, I'm outta here!");
- }
- // This code would normally appear outside the class!
- public static void main(String[] args) {
- Elvis elvis = Elvis.INSTANCE;
- elvis.leaveTheBuilding();
- }
- }
问题:但是客户端可以通过AccessibleObject.setAccessible方法,通过反射机制调用私有构造器。
AccessibleObject 类是 Field、Method 和 Constructor 对象的基类。它提供了将反射的对象标记为在使用时取消默认 Java 语言访问控制检查的能力。对于公共成员、默认(打包)访问成员、受保护成员和私有成员,在分别使用 Field、Method 或 Constructor 对象来设置或获得字段、调用方法,或者创建和初始化类的新实例的时候,会执行访问检查。
在反射对象中设置 accessible 标志允许具有足够特权的复杂应用程序(比如 Java Object Serialization 或其他持久性机制)以某种通常禁止使用的方式来操作对象。
2、公有的成员是个静态工厂方法
这种方法很清晰的表明了这个类是一个Singleton
- // Singleton with static factory - Page 17
- public class Elvis {
- private static final Elvis INSTANCE = new Elvis();
- private Elvis() { }
- public static Elvis getInstance() { return INSTANCE; }
- public void leaveTheBuilding() {
- System.out.println("Whoa baby, I'm outta here!");
- }
- // This code would normally appear outside the class!
- public static void main(String[] args) {
- Elvis elvis = Elvis.getInstance();
- elvis.leaveTheBuilding();
- }
- }
问题:同上
3、实现Singleton类变成可序列化的,仅仅实现序列化是不够的,为了维护并保证Singleton,必须声明所有实例域都是瞬时(transient)的,并提供一个readResolve方法。否则,每次反序列化一个序列的实例时,都会创建一个新的实例。
《The readResolve Method -- 序列化实现readResolve方法的作用》http://blog.csdn.net/partner4java/article/details/7058741
- import java.io.Serializable;
- // Serializable singleton with public final field - Page 18
- public class Elvis implements Serializable{
- public static final Elvis INSTANCE = new Elvis();
- private Elvis() { }
- public void leaveTheBuilding() {
- System.out.println("Whoa baby, I'm outta here!");
- }
- private Object readResolve() {
- // Return the one true Elvis and let the garbage collector
- // take care of the Elvis impersonator.
- return INSTANCE;
- }
- // This code would normally appear outside the class!
- public static void main(String[] args) {
- Elvis elvis = Elvis.INSTANCE;
- elvis.leaveTheBuilding();
- }
- }
4、从JDK 1.5开始,实现Singleton,可以编写一个包含单个元素的枚举类型。
而且可以解决复杂的反序列化或者反射的攻击。
单元素的枚举类型已经成为实现Singleton的最佳方法。
- // Enum singleton - the preferred approach - page 18
- public enum Elvis {
- INSTANCE;
- public void leaveTheBuilding() {
- System.out.println("Whoa baby, I'm outta here!");
- }
- // This code would normally appear outside the class!
- public static void main(String[] args) {
- Elvis elvis = Elvis.INSTANCE;
- elvis.leaveTheBuilding();
- }
- }
-
1. 用私有构造器来强化
很简单,就是将构造器声明为private类型的,但是需要注意一点,享有特权的客户端可以利用反射机制来调用到私有的构造器,为了进一步确保单例的唯一性,我们可以在私有的构造方法中判断唯一实例是否存在,存在的话抛出异常,就像这样:
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; } }
这样做就可以绝对防止出现多个实例了么?NO!
现在还有一种情况下会出现多个实例,那就是在你序列化这个对象之后,在进行反序列化,这个时候,你将再次得到一个新的对象,不信?来吧,代码说明一切:1).首先将上面的一段代码实现Serializable接口:
public class Singleton implements Serializable{ ... }
2).开始进行序列化测试,测试代码如下(保证简洁好理解,不保证严谨性):
public class SerializableTest { @Test public void serializableTest() throws Exception{ serializable(Singleton.getInstance(), "test"); Singleton singleton = deserializable("test"); Assert.assertEquals(singleton, Singleton.getInstance()); } //序列化 private 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 <T> T deserializable(String filename) throwsIOException, ClassNotFoundException { FileInputStream fis = new FileInputStream(filename); ObjectInputStream ois = new ObjectInputStream(fis); return (T) ois.readObject(); } }
3). 运行测试:
啊偶~报错了,得到了两个不同的对象,看图:
从结果中可以看出,得到了两个不同的对象,一个Singleton@deb6432和一个Singleton@1b4fb997;
好吧,解决方案当然是有的,你只要在单例类里面加上下面这个方法:private Object readResolve() { return INSTANCE; }
在运行一遍测试代码,问题解决!
2. 利用枚举来强化Singleton(最佳方案)
利用单元素的枚举来实现单例(Singleton),绝对防止多次实例化,上面的问题在这里都不会出现,实现代码如下:
public enum SingletonEnum { INSTANCE; private String filed01; public String getFiled01() { return filed01; } public void setFiled01(String filed01) { this.filed01 = filed01; } }
使用的时候跟我们常用的单例方式也十分相似:
SingletonEnum.INSTANCE.setFiled01("123"); System.out.println(SingletonEnum.INSTANCE.getFiled01());
运行结果就不用贴出来了,大家都懂。
作者:想飞的僵尸
链接:http://www.jianshu.com/p/286231f0fa7c