《Effective Java》 读书笔记(三) 使用私有构造方法或枚举实现单例类

1.单例类到现在为止算是比较熟悉的一种设计模式了,最开始写单例模式是在C#里面,想要自己实现一个单例类,代码如下:

    public class Instance
    {
        private static readonly Instance instance = new Instance();

        public static Instance Instance
        {
            get { return instance; }
        }

        private Instance() { }
    }

嗯,这是一贯的写法。

《Effective Java》开篇是这样写的:

public class Elvis {
public static final Elvis INSTANCE=new Elvis(); private Elvis(){} }

感觉这样写也没什么问题啊。为什么自己在刚接触单例类的时候,没有想过呢,或者是太习惯将属性写成private的了,导致现在看到上面那段代码,都不能第一时间想到这有什么坏处了。

还是得多思考:如果真的上面的Elvis类一样直接将INSTANCE作为public属性传给用户,那么如果后来INSTANCE有别的什么需求,比如每次调用这个对象时,都需要内置生成一个uuid,那么只能改接口,这不符合面向对象设计的开闭原则。

而使用静态工厂方法:

public class Elvis {
    private static final Elvis INSTANCE = new Elvis();

    public static Elvis getInstance() {
        return INSTANCE;
    }

    private Elvis() {
    }

}

我们只需要在getInstance里面添加一条代码即可,而这对于用户来说,是感觉不到的。

2.通过静态工厂方法创建单例类的另一个好处是这样写能支持JDK 1.8 新特性:Suppiler

3.为了保证单例是真正的单例的(#)最好使用枚举实现单例模式,使用枚举能够防御各种实例化攻击。

(#:)PS:这里感觉得多说一点:

上面的静态工厂方法提供INSTANCE单例类是平时最常使用的一种方法,但是还有很多的实现方式,具体的自行查找吧。

这里我要说的是:其实上面的方法都不能完全保证程序在整个运行的时间都只有一个这个对象。可以查找一下:单例类与序列化单例类与反射

防卫单例类与序列化的方法在于实现

private Object readResolve(){
    return INSTANCE;
}

  原理在于如果序列化过程中readResolve返回为空,则使用反射调用私有构造方法重新构造一个对象。

方位反射的方法在于:

反射主要是能够绕过private的权限机制,使得用户能够使用private的构造方法。

因此我们再构造函数里面添加一个判断,如果第二次调用此函数,则抛出异常即可。

明白了序列化攻击和反射攻击以后,就能大概判断哪些方法能够防止这种情况出现,那种方法不能防止。

因此,使用枚举方法是最好的选择。(此处应该有篇博客详细解释一下)

原文地址:https://www.cnblogs.com/dengchengchao/p/9035252.html