本文讲Java中单例模式的3种实现方法。
方法一
public class Elvis {
public static final Elvis INSTANCE=new Elvis();
private Elvis(){}
}
强制把构造函数私有化,只能通过INSTANCE来得到实例,并且这个实例只有1个。同时,INSTANCE中final类型,不可被更改。
但是客户端程序可以借助AccessibleObject.setAccessible的方法,通过反射来调用私有构造函数。代码如下:
public class Elvis {
private static int count=0;
public static final Elvis INSTANCE=new Elvis();
private Elvis(){
count++;
}
public static void main(String[] args){
System.out.println(Elvis.INSTANCE.count);
Class<?> cl=null;
try {
//通过反射得到类
cl=Class.forName("singleton.Elvis");
//获取类的构造函数
Constructor constructors[]=cl.getDeclaredConstructors();
//设置构造函数可访问
constructors[0].setAccessible(true);
//使用构造函数创建实例
constructors[0].newInstance(null);
//获取第一个成员(即count)的值
System.out.println(cl.getDeclaredFields()[0].get(null));
constructors[0].newInstance(null);
System.out.println(cl.getDeclaredFields()[0].get(null));
} catch (ClassNotFoundException |
IllegalArgumentException |
IllegalAccessException |
SecurityException |
InstantiationException |
InvocationTargetException e) {
e.printStackTrace();
}
}
}
输出:1
2
3
java在反射创建一个类的实例时,默认会检测是否符合相关安全,该检测开关可以关闭。Constructor、Field、Method都是继承于AccessibleObject,对应实例调用setAccessible(true)就关闭该开关。
如果要抵御这种反射攻击,可以修改构造函数,让它在创建第二个实例时抛出异常。
private Elvis(){
if(count==1){
System.err.println("transgress singleton");
System.exit(1);
}
count++;
}
方法二
使用工厂方法。工厂方法本身在诸多好处,这里就不细讲了。
public class Elvis2 {
private static final Elvis2 INSTANCE=new Elvis2();
private Elvis2(){}
public static Elvis2 getInstance(){
return INSTANCE;
}
public static void main(String[] args){
Elvis2 inst1=Elvis2.getInstance();
Elvis2 inst2=Elvis2.getInstance();
if(inst1==inst2)
System.out.println("inst1==inst2");
else
System.out.println("inst1!=inst2");
}
}
输出:inst1==inst2
当然这也会遇到反射攻击。
为了使Singeleton类变成可序列化的(Serializable),仅仅在声明中加上"implements Serializable"是不够的。为了维护并保证Singeleton,必须声明所有实例域都是瞬时的(transient),并提供一个readResolve方法,否则每次反序列化的实例时,都会创建一个新的实例。所以就有了第三种方法。
方法三
编写一个包含单个元素的枚举类型。
public enum Elvis3 {
INSTANCE;
public int count=0;
public void incease(){
count++;
}
public static void main(String[] args){
Elvis3 inst1=Elvis3.INSTANCE;
inst1.incease();
inst1.incease();
System.out.println(inst1.count);
Elvis3 inst2=Elvis3.INSTANCE;
System.out.println(inst2.count);
}
}
单元素枚举类型已经成为实现Singeleton的最佳方式,它更加简洁,无偿提供了序列化机制,绝对防止多次实例化(即使使用复杂的序列化或反射攻击)。