实现单例模式的3种简单方式
我们知道,在面向对象里面,类和对象一般是一对多的关系。一个类可以被实例化为多个对象,但是在我们开发过程中,有些地方要求我们必须使用使用同一个对象,也就是我们多次实例化一个类,得到的是同一个对象,这便是设计模式中的单例模式。
前两种实现思路比较简单,就是将构造器私有化,暴露公有的静态成员。
方式一:公有的静态成员为实例
public class Student{
//导出公用的静态成员
public static final Student INSTANCE = new Student();
//私有化构造器
private Student(){};
}
因为我们使用final对INSTANCE进行修饰,因此它仅仅可以被实例化一次,保证全局唯一。
方式二:共有的静态成员为静态工厂方法
public class Student{
private static final Student INSTANCE = new Student();
//共有的静态工厂方法
public static Student getInstance(){
return INSTANCE;
}
//私有化构造器
private Student(){};
}
相对于方式一,方式二具有如下优势:
-
灵活可以被改为每个线程返回一个唯一实例
-
可以编写泛型单例工厂
-
可以使用Java8之后的方法引用 Student::getInstance
没有上述要求的,推荐方式一
两种方式都无法避免被攻击者使用反射机制来调用私有的构造方法。
解决方案是在构造器中在被第二次实例化时抛出异常。
无论是使用方式一还是方式二,当我们的单例是需要变为可序列化的时候,不能仅仅是使用implements Serializable就感觉万事大吉了,否则在每次反序列化时都会创建新的实例,也将不再是单例模式了。要想继续保证单例,我们必须实现readResolve()方法,如下:
import java.io.Serializable;
public class Student implements Serializable{
private static final Student INSTANCE = new Student();
//共有的静态工厂方法
public static Student getInstance(){
return INSTANCE;
}
//私有化构造器
private Student(){};
private Object readResolve(){
return INSTANCE;
}
}
再进行反序列化时,会创建一个Student的新实例,这个实例并非是之前的INSTANCE实例。而对于正确实现了readResolve()方法的类,在反序列化完成之后,会将readResolve()方法返回的实例替换掉反序列化生成的新实例,取代因为反序列化而产生的新对象。指向新对象的引用不再保留,因此立即会变为被GC回收的对象。这样就保证了实例的唯一性。
方式三:声明一个包含单个元素的枚举类型
public enum Student{
INSTANCE;
public void otherMethod(){}
}
这种方式最简洁,提供序列化机制,在复杂序列化或者反射攻击时都可以防止多次实例化。当Student需要继承父类时,此方法不宜使用。
记录于2020年4月29日21:29:35 阅读《Effective Java》第3条