注册式单例
1.枚举式单例
注册式单例又称为登记式单例,就是将每一个实例都登记到某一个地方,使用唯一的标 识获取实例。注册式单例有两种写法:一种为容器缓存,一种为枚举登记。先来看枚举 式单例的写法,来看代码,创建 EnumSingleton 类
package com.gupaoedu.vip.pattern.singleton.register;
/**
* 注册式单例方式1:枚举式单例
*/
public enum EnumSingleton {
INSTANCE;
private Object data;
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
public static EnumSingleton getInstance(){
return INSTANCE;
}
}
测试:
package com.gupaoedu.vip.pattern.singleton.register;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
public class EnumSingletonTest {
public static void main(String[] args) {
EnumSingleton s1 = null;
EnumSingleton s2 = EnumSingleton.getInstance();
s2.setData(new Object());
FileOutputStream fos = null;
try {
fos = new FileOutputStream("EnumSingleton.obj") ;
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(s2);
oos.flush();
oos.close();
FileInputStream fis = new FileInputStream("EnumSingleton.obj");
ObjectInputStream ois = new ObjectInputStream(fis);
s1 = (EnumSingleton)ois.readObject();
System.out.println(s1.getData());
System.out.println(s2.getData());
System.out.println(s1 == s2);
} catch (Exception e){
e.printStackTrace();
}
}
}
输出:
原理探究:反编译EnumSingleton.class
package com.gupaoedu.vip.pattern.singleton.register;
public final class EnumSingleton extends Enum
{
public static EnumSingleton[] values()
{
return (EnumSingleton[])$VALUES.clone();
}
public static EnumSingleton valueOf(String name)
{
return (EnumSingleton)Enum.valueOf(com/gupaoedu/vip/pattern/singleton/register/EnumSingleton, name);
}
private EnumSingleton(String s, int i)
{
super(s, i);
}
public Object getData()
{
return data;
}
public void setData(Object data)
{
this.data = data;
}
public static EnumSingleton getInstance()
{
return INSTANCE;
}
public static final EnumSingleton INSTANCE;
private Object data;
private static final EnumSingleton $VALUES[];
static
{
INSTANCE = new EnumSingleton("INSTANCE", 0);
$VALUES = (new EnumSingleton[] {
INSTANCE
});
}
}
发现枚举式单例的实例,其实是在静态块里面的,也就是饿汉式单例,这样绝对保证了线程安全;那么序列化能否破坏枚举式单例呢?
是回到 ObjectInputStream 的 readObject0()方法
readObject0()中调用了 readEnum()方法,来看 readEnum()中代码实现:
我们发现枚举类型其实通过类名和 Class 对象类找到一个唯一的枚举对象。因此,枚举对 象不可能被类加载器加载多次。那么反射是否能破坏枚举式单例呢?
package com.gupaoedu.vip.pattern.singleton.register;
import java.lang.reflect.Constructor;
public class EnumSingletonReflectTest {
public static void main(String[] args) {
try {
Class clazz = EnumSingleton.class;
Constructor c = clazz.getDeclaredConstructor();
c.setAccessible(true);
c.newInstance();
} catch (Exception e){
e.printStackTrace();
}
}
}
报的是 java.lang.NoSuchMethodException 异常,意思是没找到无参的构造方法。这 时候,我们打开 java.lang.Enum 的源码代码,查看它的构造方法,只有一个 protected 的构造方法,代码如下:
既然这样,那是否可以获取有参构造,继续通过反射破坏单例呢?
package com.gupaoedu.vip.pattern.singleton.register;
import java.lang.reflect.Constructor;
public class EnumSingletonReflectTest {
public static void main(String[] args) {
try {
Class clazz = EnumSingleton.class;
Constructor c = clazz.getDeclaredConstructor(String.class,int.class);
c.setAccessible(true);
EnumSingleton obj = (EnumSingleton)c.newInstance("tom", 999);
System.out.println(obj);
} catch (Exception e){
e.printStackTrace();
}
}
}
这时报如下错误:Cannot reflectively create enum objects
java.lang.IllegalArgumentException: Cannot reflectively create enum objects
at java.lang.reflect.Constructor.newInstance(Constructor.java:417)
at com.gupaoedu.vip.pattern.singleton.test.EnumSingletonReflectTest.main(EnumSingletonReflectTest.java:26)
Process finished with exit code 0
查看构造方器的newInstance方法,如下:
在 newInstance()方法中做了强制性的判断,如果修饰符是 Modifier.ENUM 枚举类型, 直接抛出异常。
枚举式单例也是《Effective Java》书中推荐的一种单例实现写法。在 JDK 枚举的语法特殊性,以及反射也为枚举保 驾护航,让枚举式单例成为一种比较优雅的实现