《Effective Java》一书中第三条经验指出:“用私有构造器或者枚举类型强化Singleton”,其中先提出了两种单例模式。
1.暴露静态final字段
public class Elvis {
public static final Elvis INSTANCE = new Elvis();
private Elvis(){;;;}
public void leaveTheBuilding() {;;;}
}
2.暴露静态方法
public class Elvis {
private static final Elvis INSTANCE = new Elvis();
private Elvis(){;;;}
public static Elvis getInstance() { return INSTANCE;}
public void leaveTheBuilding() {;;;}
}
但是如果借助AccessibleObject.setAccessible方法,通过反射机制调用私有构造器,都可以创建新的对象绕过了Java的私有语义,可以通过增加一个计数器来统计创建的对象个数,通过抛出异常的形式来阻止创建新的对象。
package hello;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
/**
* Singleton
*
* @author crystalChen
* @date 16/6/4 11:38
*/
public class Singleton {
private static int cntInstance = 0; //防止第二次new对象
private static volatile Singleton instance = null;
private Singleton() throws Exception{
if (++cntInstance > 1)
throw new Exception("try to new more than one singleton.");
System.out.println("new a singleton.");
}
public static Singleton getInstance() throws Exception{
if (null == instance) { //为了简洁度,不考虑线程安全
instance = new Singleton();
}
return instance;
}
public static void main(String[] args) {
try {
Singleton.getInstance();
Class singletonClass = Class.forName("hello.Singleton");
// Constructor[] cts = singletonClass.getConstructors(); 这种方式找不到私有构造器
Constructor[] cts = singletonClass.getDeclaredConstructors();
System.out.println("size: " + cts.length);
cts[0].setAccessible(true); //访问权限打开setAccessible(true),就可以访问私有构造函数
Singleton singleton = (Singleton)cts[0].newInstance();
} catch(ClassNotFoundException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
}
抛出异常:/Library/Java/JavaVirtualMachines/jdk1.8.0_51.jdk/Contents/Home/bin/java -agentlib:jdwp=transport=dt_socket,address=127.0.0.1:63750,suspend=y,server=n -Dfile.encoding=UTF-8 -classpath "/Library/Java/JavaVirtualMachines/jdk1.8.0_51.jdk/Contents/Home/lib/ant-javafx.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_51.jdk/Contents/Home/lib/dt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_51.jdk/Contents/Home/lib/javafx-mx.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_51.jdk/Contents/Home/lib/jconsole.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_51.jdk/Contents/Home/lib/packager.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_51.jdk/Contents/Home/lib/sa-jdi.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_51.jdk/Contents/Home/lib/tools.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_51.jdk/Contents/Home/jre/lib/charsets.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_51.jdk/Contents/Home/jre/lib/deploy.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_51.jdk/Contents/Home/jre/lib/javaws.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_51.jdk/Contents/Home/jre/lib/jce.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_51.jdk/Contents/Home/jre/lib/jfr.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_51.jdk/Contents/Home/jre/lib/jfxswt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_51.jdk/Contents/Home/jre/lib/jsse.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_51.jdk/Contents/Home/jre/lib/management-agent.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_51.jdk/Contents/Home/jre/lib/plugin.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_51.jdk/Contents/Home/jre/lib/resources.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_51.jdk/Contents/Home/jre/lib/rt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_51.jdk/Contents/Home/jre/lib/ext/cldrdata.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_51.jdk/Contents/Home/jre/lib/ext/dnsns.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_51.jdk/Contents/Home/jre/lib/ext/jfxrt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_51.jdk/Contents/Home/jre/lib/ext/localedata.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_51.jdk/Contents/Home/jre/lib/ext/nashorn.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_51.jdk/Contents/Home/jre/lib/ext/sunec.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_51.jdk/Contents/Home/jre/lib/ext/sunjce_provider.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_51.jdk/Contents/Home/jre/lib/ext/sunpkcs11.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_51.jdk/Contents/Home/jre/lib/ext/zipfs.jar:/Users/happyelements/Documents/javaProject/javaWeb/out/production/javaWeb:/Users/happyelements/Documents/javaProject/javaWeb/lib/javax.annotation.jar:/Users/happyelements/Documents/javaProject/javaWeb/lib/javax.ejb.jar:/Users/happyelements/Documents/javaProject/javaWeb/lib/javax.transaction.jar:/Users/happyelements/Documents/javaProject/javaWeb/lib/javax.persistence.jar:/Users/happyelements/Documents/javaProject/javaWeb/lib/javax.resource.jar:/Users/happyelements/Documents/javaProject/javaWeb/lib/javax.jms.jar:/Users/happyelements/Documents/javaProject/javaWeb/lib/javax.servlet.jar:/Users/happyelements/Documents/javaProject/javaWeb/lib/javax.servlet.jsp.jstl.jar:/Users/happyelements/Documents/javaProject/javaWeb/lib/javax.servlet.jsp.jar:/Applications/IntelliJ IDEA 14.app/Contents/lib/idea_rt.jar" hello.Singleton
Connected to the target VM, address: '127.0.0.1:63750', transport: 'socket'
new a singleton.
size: 1
java.lang.reflect.InvocationTargetException
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.lang.reflect.Constructor.newInstance(Constructor.java:422)
at hello.Singleton.main(Singleton.java:41)
Caused by: java.lang.Exception: try to new more than one singleton.
at hello.Singleton.<init>(Singleton.java:20)
... 5 more
Disconnected from the target VM, address: '127.0.0.1:63750', transport: 'socket'
Process finished with exit code 0
但是如果想序列化单例的话,除了实现Serializable接口,注意:
1. 声明所有实例域都是transient的;
2.提供一个readResolve方法,返回单例对象。否则,反序列化的时候,会构建一个新的实例,违反了单例的原则。
private Object readResolve() {
return <span style="font-family: Helvetica, Tahoma, Arial, sans-serif;">instance</span>;
}
在JDK1.5及以后,增加了实现Singleton的第三种方法。只需编写一个包含单个元素的枚举类型。
<span style="font-family:Helvetica, Tahoma, Arial, sans-serif;">public enum Elvis {
INSTANCE; //只有一个元素
public void leaveTheBuilding() {;;;}
} </span>
这种方法更加简洁,无偿地提供序列化机制,绝对安全。虽然这种方法还没有广泛采用,但是单元素的枚举类型已经成为实现Singleton的最佳方法。