私有构造方法
1、使用私有构造函数强化singleton属性
方法一:公有的静态成员是一个final域,成员的声明很清楚的表达了这个类是一个singleton。
1 public class Elvis { 2 public static final Elvis INSTANCE = new Elvis(); 3 4 private Elvis() { } 5 6 public void leaveTheBuilding() { 7 System.out.println("This is singleton."); 8 } 9 10 public static void main(String[] args) { 11 Elvis elvis = Elvis.INSTANCE; 12 elvis.leaveTheBuilding(); 13 } 14 }
私有的构造函数仅被调用了一次,用来实例化公有的静态final域Elvis.INSTANCE; 由于缺少公有的或者受保护的构造函数,所以保证了Elvis的全局唯一性。
一旦Elvis类被实例化之后,只有一个Elvis实例存在。
方法二:提供一个公有的静态工厂方法,而不是公有的静态final域。该方式提供了更大的灵活性,在不改变API的前提下,可以把该类改成singleton或者非singleton的。
1 public class Elvis { 2 private static final Elvis INSTANCE = new Elvis(); 3 4 private Elvis() { } 5 6 public static Elvis getInstance() { return INSTANCE; } 7 8 public void leaveTheBuilding() { 9 System.out.println("This is singleton."); 10 11 } 12 13 public static void main(String[] args) { 14 Elvis elvis = Elvis.getInstance(); 15 elvis.leaveTheBuilding(); 16 } 17 }
但是,这是存在问题的。
不考虑反射这种极端情况,还有一种情况会破坏单例模式。
序列化,反序列化!
1 package cp2; 2 3 import java.io.Serializable; 4 import java.util.Objects; 5 6 7 public class SerSingleton implements Serializable { 8 String name; 9 private SerSingleton(){ 10 System.out.println("Singleton is creating"); 11 } 12 13 private static SerSingleton instance = new SerSingleton(); 14 15 public static SerSingleton getInstance(){ 16 return instance; 17 } 18 19 public static void createString(){ 20 System.out.println("create string in singleton"); 21 } 22 }View Code
测试代码:
1 import cp2.SerSingleton; 2 import junit.framework.Assert; 3 import org.junit.Test; 4 5 import java.io.FileInputStream; 6 import java.io.FileOutputStream; 7 import java.io.ObjectInputStream; 8 import java.io.ObjectOutputStream; 9 10 11 public class SerSingletonTest { 12 13 @Test 14 public void test() throws Exception{ 15 SerSingleton s1 = null; 16 SerSingleton s = SerSingleton.getInstance(); 17 18 FileOutputStream fos = new FileOutputStream("a.txt"); 19 ObjectOutputStream oos = new ObjectOutputStream(fos); 20 oos.writeObject(s); 21 oos.flush(); 22 oos.close(); 23 24 FileInputStream fis = new FileInputStream("a.txt"); 25 ObjectInputStream ois = new ObjectInputStream(fis); 26 s1 = (SerSingleton) ois.readObject(); 27 28 Assert.assertEquals(s,s1); 29 } 30 }View Code
结果:
1 junit.framework.AssertionFailedError: 2 Expected :cp2.SerSingleton@7ab2bfe1 3 Actual :cp2.SerSingleton@497470ed 4 <Click to see difference>
问题来了,怎么解决呢?jdk其实预料到这种情况了。
解决方法:加入readResolve()
在jdk中ObjectInputStream的类中有readUnshared()方法,上面详细解释了原因。我简单描述一下,那就是如果被反序列化的对象的类存在readResolve这个方法,他会调用这个方法来返回一个“array”(我也不明白),然后浅拷贝一份,作为返回值,并且无视掉反序列化的值,即使那个字节码已经被解析。
所以,完整的单例模式是:
1 package cp2; 2 3 import java.io.Serializable; 4 import java.util.Objects; 5 6 /** 7 * Created by dubby on 16/3/25. 8 */ 9 public class SerSingleton implements Serializable { 10 String name; 11 private SerSingleton(){ 12 System.out.println("Singleton is creating"); 13 } 14 15 private static SerSingleton instance = new SerSingleton(); 16 17 public static SerSingleton getInstance(){ 18 return instance; 19 } 20 21 public static void createString(){ 22 System.out.println("create string in singleton"); 23 } 24 25 private Object readResolve(){ 26 System.out.println("read resolve"); 27 return instance; 28 } 29 }View Code
总结
- 第一种方法效率稍微高一些,采用第一种方法实现singleton后,就没有改变的余地了,当你想把该类改成非singleton,显然是不行的了。所以,除非确实该类是一个singleton,那就用第一个方法吧。
- 用第2种方法的时候,假如该类实现了serializable接口,那应该重写readResolve()方。否则再反序列化的时候是会产生一个新的实例,这与singleton相违背了
2、通过私有的构造函数强化不可实例化的能力
在面向对象程序设计中,假如存在太多只有静态属性和静态方法的类;那么,面向对象的思想可能在这会损失殆尽。
但是,并不能说面向对象的程序中就不应该出现只有静态属性和静态方法的类,相反,有时候我们还必须写这样的类作为工具类。
这样的类怎么实现呢?有人可能会把该类定义成抽象类(Abstract class),的确,抽象类是不可以实例化的,但是别忘了还有继承,继承了抽象类的子类在实例化时候,默认是会先调用父类无参数的构造函数的(super();),这时候,父类不是也被实例化了嘛?
其实我们可以这样做,把该类的构造函数定义为私有的(private),而类的内部又不调用该构造函数的话,就成功了。
这样带来的后果就是该类成了 final的,不可能再被任何类继承了,要被继承,得提供一个公有(public)的或者保护(protect)的构造函数, 这样才能被子类调用。