用枚举增强单例模式的可靠性 - Effective Java 中文版第二版的读书心得(三)

 我们常用的构造单例模式(Singleton)的方法,一般有2种

1 提供一个静态的公共属性
2 提供一个静态的公共方法

这2个方法,都是采用了私有的构造器来防止外部直接构造实例。 但我们可以用反射的方法,获得多个实例。后面我会给出测试的代码。

从1.5开始,枚举也可以用来获得单例,而且更加可靠。同时又自动提供了一些额外的功能。

先看看测试代码:

import java.lang.reflect.Constructor; /** * 测试Singleton的可靠性。 * * @author 老紫竹(laozizhu.com) */ public class TestSingleton { public static void main(String[] args) { testSingleton1(); testSingleton2(); testSingleton3(); } public static void testSingleton1() { try { // 测试Singletom1 // 拿到第一个实例 TestSingleton1 s1 = TestSingleton1.getInstance(); // 测试拿到第二个实例 Class c1 = Class.forName("TestSingleton1"); Constructor[] cons = c1.getDeclaredConstructors(); Constructor cc1 = cons[0]; cc1.setAccessible(true); TestSingleton1 s2 = (TestSingleton1) cc1.newInstance(null); System.out.println(s1 + "/" + s2); System.out.println(s1 == s2); } catch (Exception ex) { ex.printStackTrace(); } } public static void testSingleton2() { try { // 测试Singletom1 // 拿到第一个实例 TestSingleton2 s1 = TestSingleton2.getInstance(); // 测试拿到第二个实例 Class c1 = Class.forName("TestSingleton2"); Constructor[] cons = c1.getDeclaredConstructors(); Constructor cc1 = cons[0]; cc1.setAccessible(true); TestSingleton2 s2 = (TestSingleton2) cc1.newInstance(null); System.out.println(s1 + "/" + s2); System.out.println(s1 == s2); } catch (Exception ex) { ex.printStackTrace(); } } public static void testSingleton3() { try { // 测试Singletom1 // 拿到第一个实例 TestSingleton3 s1 = TestSingleton3.getInstance(); // 测试拿到第二个实例 Class c1 = Class.forName("TestSingleton3"); Constructor[] cons = c1.getDeclaredConstructors(); Constructor cc1 = cons[0]; cc1.setAccessible(true); TestSingleton3 s2 = (TestSingleton3) cc1.newInstance(null); System.out.println(s1 + "/" + s2); System.out.println(s1 == s2); } catch (Exception ex) { ex.printStackTrace(); } } } /** * 一个普通的Singletone实现。 * * @author 老紫竹(laozizhu.com) */ class TestSingleton1 { private static final TestSingleton1 INSTANCE = new TestSingleton1(); public static TestSingleton1 getInstance() { return INSTANCE; } private TestSingleton1() { } } /** * 一个用异常强化了的Singletone实现。 * * @author 老紫竹(laozizhu.com) */ class TestSingleton2 { private static final TestSingleton2 INSTANCE = new TestSingleton2(); public static TestSingleton2 getInstance() { return INSTANCE; } private static boolean initSign; private TestSingleton2() { if (initSign) { throw new RuntimeException("实例只能建造一次"); } initSign = true; } } /** * 枚举实现的Singleton * * @author 老紫竹(laozizhu.com) */ enum TestSingleton3 { INSTANCE; public static TestSingleton3 getInstance() { return INSTANCE; } }


测试结果
TestSingleton1@c17164/TestSingleton1@1fb8ee3
false
java.lang.reflect.InvocationTargetException
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:39)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:27)
at java.lang.reflect.Constructor.newInstance(Constructor.java:513)
at TestSingleton.testSingleton2(TestSingleton.java:43)
at TestSingleton.main(TestSingleton.java:11)
Caused by: java.lang.RuntimeException: 实例只能建造一次
at TestSingleton2.<init>(TestSingleton.java:103)
... 6 more
java.lang.IllegalArgumentException: Cannot reflectively create enum objects
at java.lang.reflect.Constructor.newInstance(Constructor.java:511)
at TestSingleton.testSingleton3(TestSingleton.java:61)
at TestSingleton.main(TestSingleton.java:12)


小结:可见,只有第三种枚举的方法才是最安全的。

关于里面提到的,在序列化是可能出现的问题,我看以后在讨论吧。不过因为枚举实现的单例没有这个问题,所以我看以后就用枚举好了,何必自己跟自己过不去呢?

你可能感兴趣的:(java,exception,测试,读书,Class,Constructor)