Effective Java(2)之用私有构造器或者枚举类型强化Singleton属性
书上分别说了以下三种
1)将公有静态成员做成final域享有特权的客户端可以借助AccessibleObject.setAccessible方法,通过反射机制调用私有构造器。
package com.lzw.singleton1; import java.lang.reflect.Constructor; /** * * 单例实现1 * <p> * 公有静态成员是个public final域 * <p> * 为防止client利用反射调用私有改造函数,所以在创建第二个实例的时候抛出了异常 * * @author troyli * */ public class Singleton1 { public static final Singleton1 INSTANCE = new Singleton1(); // 私有改造函数 private Singleton1() { if (INSTANCE != null) { throw new IllegalArgumentException("不存在第二个实例对象……"); } } // 其他方法实现 public void otherMethod() { System.out.println("call otherMethod"); } @SuppressWarnings({ "rawtypes", "unchecked" }) public static void main(String args[]) throws Exception { Singleton1 singleton = Singleton1.INSTANCE; singleton.otherMethod(); // 利用反射调用私有构造器 Constructor[] arrayConstructor = singleton.getClass() .getDeclaredConstructors(); for (Constructor constructor : arrayConstructor) { // 调用setAccessible(true); constructor.setAccessible(true); // 实例化,这里一定会抛出异常 constructor.newInstance(); } } }
问题:为什么会抛出异常(不存在第二个实例对象……),这里我们分析一下代码的执行顺序。
①JVM 装载类的静态成员变量:public static final Singleton1 INSTANCE;
②执行入口方面main;
③获取实例对象:Singleton1 singleton = Singleton1.INSTANCE;也就会执行private Singleton1(){……}
④执行newInstatnce:constructor.newInstance();就会是在已经有一个对象的时候重新调用private Singleton1(){……},于是就会抛出异常。
2)将公有静态成员做成final域和将公有成员做成静态工厂方法时,为了使Singleton类变成可序列化的,仅仅在声明上加上“implements Serializable”是不够的。
为了维护并保证Singleton,必须声明所有实例域都是瞬时的(transient),并提供一个readResolve方法。否则,每次反序列化一个序列化的实例时,都会创建一个新的实例。
package com.lzw.singleton2; import java.io.Serializable; /** * *单例实现2 *<p> * 公有的成员为静态工厂方法 *<p> * 序列化时,要实现readResolve方法,防止反序列化出新的实例(这个应该怎么测试呢???) * *@author troyli * */ public class Singleton2 implements Serializable { // 私有static Instance private static final Singleton2 INSTANCE = new Singleton2(); // 私有构造函数 private Singleton2() { } // 获取单例方法 public static Singleton2 getInstance() { return INSTANCE; } // 其他方法 public void otherMethod() { // } // 必须提供该方法,以便重新指定反序列化得到的对象. private Object readResolve() { return INSTANCE; } }
这点对我是全新的东西,因此也在积累中……
3)单元素枚举类型更加简洁、无偿的提供了序列化机制,绝对防止多次实例化,即使是在面对复杂的序列化或者反射攻击时;它已经成为实现Singleton的最佳方法。
package com.lzw.singleton3; import java.lang.reflect.Constructor; /** * *枚举实现单例 *<p> * 目前最好的方式,避免了反射的攻击和序列化的问题 * *<pre> * 射调用枚举私有构造函数测试结果: * Exception in thread "main" java.lang.IllegalArgumentException: Cannot reflectively create enum objects at java.lang.reflect.Constructor.newInstance(Unknown Source) at com.lzw.singleton3.Singleton3.main(Singleton3.java:41) *</pre> * *@author troyli * */ public class Singleton3 { enum EnumSingleton { INSTANCE; public void otherMethod() { } } // 测试,是否可以反射生成枚举 public static void main(String args[]) throws Exception { // 利用反射调用私有构造器 Constructor[] arrayConstructor = EnumSingleton.INSTANCE.getClass() .getDeclaredConstructors(); for (Constructor constructor : arrayConstructor) { // 调用setAccessible(true),设置为可以访问; constructor.setAccessible(true); // 实例化,这里一定会抛出异常 constructor.newInstance(); } } }
可以看上面的第一种的问题解析,这里不再重复。
虽然这种方法目前还没有被广泛采用,但是单元素的枚举类型已经成为实现Singleton的最佳方法。