真正的单例模式
今天看effective java是看到上面对单例模式的介绍说,把构造器设置成私有并不能完全实现单例,因为可以通过反射来调用这个构造器,从而构造出不同的实例,今天我做了一个测试,果真是这样,下面是我的代码。
Singleton.java
1 package com.swjtu.crazykid; 2 3 public class Singleton { 4 private static Singleton singleton = new Singleton(); 5 private Singleton(){ 6 if(singleton != null) 7 throw new IllegalAccessError(); 8 } 9 10 public static Singleton getInstance(){ 11 return singleton; 12 } 13 14 }
下面是测试类:
1 package com.swjtu.crazykid.test; 2 3 import java.lang.reflect.Constructor; 4 import java.lang.reflect.Field; 5 import java.lang.reflect.InvocationTargetException; 6 7 import org.junit.Test; 8 9 import com.swjtu.crazykid.Singleton; 10 11 public class MyTest { 12 @Test 13 public void TestSingleton(){ 14 Singleton singleton1 = Singleton.getInstance(); 15 System.out.println(singleton1.hashCode()); 16 Class clazz = Singleton.class; 17 Constructor[] c; 18 try { 19 c = clazz.getDeclaredConstructors(); 20 c[0].setAccessible(true); 21 Singleton singleton = (Singleton) c[0].newInstance(); 22 System.out.println(singleton.hashCode()); 23 } catch (SecurityException e) { 24 // TODO Auto-generated catch block 25 e.printStackTrace(); 26 } catch (InstantiationException e) { 27 // TODO Auto-generated catch block 28 e.printStackTrace(); 29 } catch (IllegalAccessException e) { 30 // TODO Auto-generated catch block 31 e.printStackTrace(); 32 } catch (IllegalArgumentException e) { 33 // TODO Auto-generated catch block 34 e.printStackTrace(); 35 } catch (InvocationTargetException e) { 36 // TODO Auto-generated catch block 37 e.printStackTrace(); 38 } 39 } 40 }
如果把singleton.java的第六和第7行注释掉,会发现测试类的第15行和第22行的输出的hashcode是不一样的。所以这样做并不能做到真正的单例。当我们把singleton中的构造器做些修改,就像上面的代码一样,当里面的实例不是空的时候,调用构造器的时候抛出异常,就能解决了。
但是,同样的通过反射将这个实例设为空,你会发现照样能构造出两个不同的singleton实例。所以这样也不能真正的做到单例,就是说这样设计的单例无法抵御反射这类的攻击。书上说完美的做法是用enum。