就像强哥在上篇推文说的《Effective Java 第三版》 含Java8、Java9内容,今天我们就开始Effective Java之旅。
单例这个东西,可是说是Java基础面试必考问题,不管大小公司,提问率极高,有的面试官甚至直接让你写出具体的代码实现。所以,搞清楚单例的实现并且懂得接下来所说的,绝对能够让面试官对你有更好的印象。
Singleton(单例)就是指程序从运行开始到结束,仅仅被实例化一次的类。比如数据库连接池的设计一般也是采用单例模式,因为数据库连接是一种数据库资源。数据库软件系统中使用数据库连接池,主要是节省打开或者关闭数据库连接所引起的效率损耗,这种效率上的损耗还是非常昂贵的,因为何用单例模式来维护,就可以大大降低这种损耗。
单例的通常有一下两种方法,很简单,记下来一般就都忘不了。第一种方法,共有静态成员是final域:
public class QiangGe{ public static final QiangGe INSTANCE = new QiangGe(); private QiangGe(){ …… //具体的一些初始化操作 }}
这种方式调用的时候,直接通过QingGe.INSTANCE属性就能够获取到唯一的类实例。
第二种方法,共有成员是个静态工厂方法:
public class QiangGe{private static final QiangGe INSTANCE = new QiangGe();private QiangGe(){ …… //具体的一些初始化操作 } public static QiangGe getInstance(){ return INSTANCE; }}
这种方式调用的时候,直接通过QingGe.getInstance()方法就能够获取到唯一的类实例。
第一种方法的优势在于简单,而且很容易让你看出来这个类是个单例。而第二种方法的优势在于它更灵活,在不改变API的前提下,我们可以改变getInstance方法的实现来改变该类是否应该是单例的想法,如在实现中改成每个线程返回唯一的类实例。另外,在Java8中,这种工厂方式也可以直接通过方法引用作为提供者:QiangGe::getInstance就是一个提供者Supplier<QiangGe>。
不过,需要提到的一点也是面试官经常会问到的一点就是,按上面的方式,是否有方法在程序中生成两个类实例。答案是有的,一般可以通过反射或者反序列化的方式来做到这种方式。
- 反射:借助反射中的AccessibleObject.setAccessible方法,通过反射来生成新的类实例。
- 反序列化:如果该类实现了Serializable接口,则每次反序列化一个序列化实例时,都会创建一个新对象。
避免上面的问题的解决方法:
- 反射:可通过修改构造器,让他在需要创建第二个实例时抛异常。
- 反序列化:需要在类中添加readResolve方法,readResolve方法作用是:当JVM从内存中反序列化地"组装"一个新对象时,就会自动调用 readResolve方法来返回我们指定好的对象:
private Object readResolve(){ return INSTANCE;}
好了,上面就是我们一般也比较常用的单例实现了。可是其实还有一种方法更简单好用,面试提一提没准就能加分。那就是枚举了。
public enum QiangGe{ INSTANCE;}
这种方法与上面的第一种方法相似,单更加简洁,且无偿的提供了序列化机制,绝对防止上面通过反射和序列化在程序中获取到过个实例的情况发生。虽然这种方法还没有推广,用的人也比较少,但是单元素枚举类型经常成为实现单例的最佳方式。
好了,这些就是在Effective Java中有提到的对单例的实现机制建议。强哥之前在面试中遇到问及单例的问题,都会替代着说一句”通过枚举类型实现单例会更好……“。面试官回应的也都是比较满意的表情拉~