用私有构造器或者枚举类型强化Singleton 属性
1. Singleton 的含义
Singleton 大部分人都知道是单例的意思,因为它一般都是我们理解设计模式时,最好理解的一个。它的作用就是 使这个类就被实例化一次。在JDK1.5 前实现有两种方式,分别是 懒汉模式、饿汉模式。
1.1 懒汉模式
- 1/**
- 2 * 懒汉模式
- 3 */
- 4public class Sengleton_Lyze {
- 5 //1,构造方法私有化
- 6 private Sengleton_Lyze(){
- 7 }
- 8
- 9 //2,创建类的位唯一实例,private static 修饰
- 10 private static Sengleton_Lyze instance;
- 11
- 12 //3,提供获取实例的方法,public static 修饰
- 13 public static Sengleton_Lyze getInstance(){
- 14 if (instance==null) {
- 15 instance=new Sengleton_Lyze();
- 16 }
- 17 return instance;
- 18 }
- 19}
为什么使用getInstance()方法来获得实例?
它可以被很灵活的修改,用户在不需要修改API的情况下,我们可以改变这个类是否是单例的。
第二个优势在泛型
1.2 饿汉模式
- 1/**
- 2 * 单例模式Sengleton
- 3 * 试用实际场合:有些对象只需要一个就够了。
- 4 * 作用:保证整个实际应用程序中某个实例有且只有一个
- 5 * 类别:饿汉模式,懒汉模式
- 6 * 区别:饿汉模式的特点,加载类时比较慢,获取比较慢。线程安全的。
- 7 *
- 8 *
- 9 */
- 10public class Sengleton_Hunger {
- 11 //1,构造方法私有化,不允许外接直接创建对象
- 12 private Sengleton_Hunger(){
- 13 }
- 14 //2,创建类的唯一实例,使用private static 修饰
- 15 private static Sengleton_Hunger instance = new Sengleton_Hunger();
- 16
- 17 //3,获取这个实例的方法
- 18 public static Sengleton_Hunger getInstance(){
- 19 return instance;
- 20 }
- 21
- 22}
运行代码
- 1public class Test {
- 2 public static void main(String[] args) {
- 3
- 4 //饿汉模式
- 5 Sengleton_Hunger s1=Sengleton_Hunger.getInstance();
- 6 Sengleton_Hunger s2=Sengleton_Hunger.getInstance();
- 7 if (s1==s2) {
- 8 System.out.println("true");
- 9 }else {
- 10 System.out.println("false");
- 11 }
- 12
- 13 //懒汉模式
- 14 Sengleton_Lyze s3=Sengleton_Lyze.getInstance();
- 15 Sengleton_Lyze s4=Sengleton_Lyze.getInstance();
- 16 if (s1==s2) {
- 17 System.out.println("一样的");
- 18 }else {
- 19 System.out.println("不同的");
- 20 }
- 21 }
- 22
- 23}
运行结果
true
一样的
1.3 序列化
为了让成为单例的类实现 序列化的(Serializable),我们仅仅在声明上加上 "implements Serializable" 是不够的。 为了维护并保证 单例,必须要声明所有实例域都是 瞬时(transient) 的,并提供一个readResolve方法。否则,每次反序列化一个序列化的实例时,都会创建一个新的实例。如,我们可以为我们的 懒汉模式 加上这个方法
- 1 private Object readResolve() {
- 2 return instance;
- 3 }
1.4 总结
都需要构造方法私有化
提供公有的静态成员,方便客户端获取类的唯一实例
序列化时需要提供额外的readResolve 方法,防止反序列化时创建对象
总体上来说,懒汉就是时间换空间,在需要获取的时候才会去实例化。而饿汉就是空间换时间,不管使用否,都会实例化一个放入内存中。饿汉模式的特点,加载类时比较慢,获取比较慢、线程安全的,饿汉反之。
但是如果客户端拥有 AccessibleObject.setAccessible() 的权限,就可以通过反射机制调用私有构造器
2 通过反射获得不同实例
2.1 举例说明
- 1public class Test {
- 2 public static void main(String[] args) throws NoSuchMethodException, SecurityException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
- 3 //饿汉模式
- 4 Constructor sengleton_Hunger = Sengleton_Hunger.class.getDeclaredConstructor();
- 5 sengleton_Hunger.setAccessible(true);
- 6 Sengleton_Hunger s1= (Sengleton_Hunger) sengleton_Hunger.newInstance();
- 7 Sengleton_Hunger s2= (Sengleton_Hunger) sengleton_Hunger.newInstance();
- 8 if (s1==s2) {
- 9 System.out.println("true");
- 10 }else {
- 11 System.out.println("false");
- 12 }
- 13
- 14 //懒汉模式
- 15 Constructor sengleton_Lyze = Sengleton_Lyze.class.getDeclaredConstructor();
- 16 sengleton_Lyze.setAccessible(true);
- 17 Sengleton_Lyze s3= (Sengleton_Lyze) sengleton_Lyze.newInstance();
- 18 Sengleton_Lyze s4= (Sengleton_Lyze) sengleton_Lyze.newInstance();
- 19 if (s1==s2) {
- 20 System.out.println("一样的");
- 21 }else {
- 22 System.out.println("不同的");
- 23 }
- 24 }
- 25
- 26}
显示的结果
false
不同的
2.2 通过异常来防止二次实例对象
public class Sltn {
private static Sltn s = null;
private static boolean flag = true;
private Sltn(){
System.out.println("flag:" + flag);
if(flag){
flag = !flag;
}else{
try {
throw new Exception("duplicate instance create error!" + Sltn.class.getName());
} catch (Exception e) {
e.printStackTrace();
}
}
}
public static Sltn getInstance(){
if(null == s){
s = new Sltn();
}
return s;
}
}
使用反射第二次创建就会抛出异常
2.3 总结
通过反射在一定的条件下,是可以使用 类私有的构造方法来获得不同对象的。
通过在类私有构造方法中加入条件,可以防止反射获得第二个实例,但写法有些多余、复杂。
3. 枚举类型
在JDK1.5之后出现了第三种实现单例模式的方式 ,只需要编写一个含有单个元素的枚举类型
public enum Elvis {
INSTANCE;
public void leaveTheBuilder(){
}
}
这种方法更加的简洁
无偿提供序列化机制,绝对防止多次实例化
可以面对复杂的序列化或者反射攻击
单元素的枚举类型已经成为了实现Singleton(单例)的最佳方法