此系列文章为本人对《Effective Java》一书的学习笔记,主要是记录对书中重点内容的理解。
既然有缘看到此文,那么希望能对你有所帮助。
本文对应原书第3条 用私有构造器或枚举类型强化Singleton属性
Singleton
是指仅被实例化一次的类,也就是单例。通常被用来代表一个无状态的对象(如函数)和系统组件。实现单例的常见的有以下两种方法:
公有静态域和静态工厂方法
公有静态域
public class Demo {
// 公有静态final域
public static final Demo INSTANCE = new Demo();
// 私有构造方法
private Demo() {
...
}
}
私有构造器仅在初始化INSTANCE
的时候被调用一次,由于外界无法直接访问该构造器,所以一但Demo
类被实例化,只会存在一个Demo
示例,这就保证了单例;
但是,事情往往没有想象的那么简单,通过Java的反射机制,使用setAccessible
方法,可以随意调用这个私有构造器。
所以我们还要加上一些判断逻辑,使得在该构造器被调用第二次的时候抛出异常。
public class Demo {
private static final boolean Flag = false;
public static final Demo INSTANCE = new Demo();
private Demo() {
if (flag) {
throw new RuntimeException("不许使用骚操作");
}
flag = true;
...
}
}
优点:
- 清晰的表明了这个类是Singleton
- 非常简单
静态工厂方法
public class Demo {
// 注意这里是private域
private static final Demo INSTANCE = new Demo();
// 私有构造方法
private Demo() {...}
public static Demo instance() {
return INSTANCE;
}
}
其实和上一个方法区别不大,原理一致,所以也得处理反射导致的私有构造器被非法调用的问题。相比之下,静态工厂方法有以下几个优势,
- 更加灵活:比如我们需要的并不是全局的单例,而是伴随线程的生命周期,在一个线程内使用同一个对象,不与其他线程共用(
Spring
就有有关于单例生命周期的设置项); - 支持泛型:在需要的情况下可以写一个泛型Singleton工厂;
- 支持方法引用:通过方法引用:
Demo::instance
成为一个提供者Supplier<Demo>
。
但如果这三点优势都不是你所关注的,那么静态工厂方法实质上并不如公有静态域来的实在。
关于序列化的问题(重点内容)
我们都知道想要实现序列化只要实现Serializable
接口即可。但对于需要实现单例的类来说还不够。
因为每一次反序列化,都会创健一个新的实例,也就破坏了我们苦心经营的单例。
解决办法
:在类中加入一个readResolve
方法(请仔细阅读注释)。
public class Demo implements Serializable {
...
// 注意这不是一个需要重写的方法(Serializable接口并没有抽象方法)
// 这个方法会在反序列化时被调用
// 返回了的静态变量INSTANCE,也就是我们要的单例(静态变量不属于一个类,它属于整个世界)
// 垃圾处理器会处理掉反序列化而产生的其他实例对象
private Object readResolve() {
return INSTANCE;
}
}
枚举方法
使用枚举其实和上述的公有静态域方法相似,但是它更加简洁
、提供了序列化机制
、绝对防止多次实例化(地表最强防御)
。
public enum Demo {
INSTANCE;
...
}
虽然这种方法现在似乎不常见,但这种单元素的枚举类型经常成为实现单例的最佳方法
。
当然也有例外的情况:如果你需要创健子类来继承
Demo
,这个方法会显得不太便捷,不建议使用
总结
当你需要用单例时,不要忘了考虑枚举,它真的很努力,很Nice。
水平有限,若文章中存在错误,恳请不吝赐教,这对我以及后面的读者都有重要意义;
若文章能够帮助到你,还望一键三连,你的支持,是我最大的动力。