《Effective Java》阅读笔记3 用私有构造器或者枚举类型强化Singleton属性

193 阅读4分钟

1.序

点击直达------->>>单例模式的正确使用方式

单例(singleton)指仅仅被实例化一次的类。单例对象通常表示无状态对象,如函数或本质上唯一的系统组件。使类成为单例会使测试它的客户端变得困难,因为不可能用模拟实现代替单例,除非它实现一个充当其类型的接口。

2. 静态成员方式

public class Singleton {

    public static final Singleton singleton = new Singleton();

    private Singleton() {
        if (singleton != null)
            throw new RuntimeException("构造异常");
    }

    public void doSomething(){}
}

在上述代码中,保持私有构造方法,提供一个静态的final类型的属性完成对象的初始化,在java中,享有特权的客户端可以通过反射机制 ==AccessibleObject.setAccessible== 来调用私有构造器,所以为了防止出现这种问题,在私有化构造方法 保证第二次调用的时候抛出异常 ,来避免通过反射调用私有构造方法生成对象

3. 用私有构造器来强化

很简单,就是将构造器声明为private类型的,但是需要注意一点,==享有特权的客户端可以利用反射机制来调用到私有的构造器==,为了进一步确保单例的唯一性,我们可以在私有的构造方法中判断唯一实例是否存在,存在的话抛出异常,就像这样:

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

        private Singleton() {
            if (INSTANCE != null) {
                throw new UnsupportedOperationException("Instance already exist");
            }
        }

        public static Singleton getInstance() {
            return INSTANCE;
        }}

这样做就可以绝对防止出现多个实例了么?NO! 现在还有一种情况下会出现多个实例,那就是在你序列化这个对象之后,在进行反序列化,这个时候,你将再次得到一个新的对象,不信?来吧,代码说明一切:

1).首先将上面的一段代码实现Serializable接口:

public class Singleton implements Serializable{
                        ...}

2).开始进行序列化测试,测试代码如下(保证简洁好理解,不保证严谨性):

public class SerializableTest {
        @Test
        public void serializableTest() throws Exception{
            serializable(Singleton.getInstance(), "test");
            Singleton singleton = deserializable("test");
            Assert.assertEquals(singleton, Singleton.getInstance());
        }

        //序列化
        private void serializable(Singleton singleton, String filename) throws IOException {
            FileOutputStream fos = new FileOutputStream(filename);
            ObjectOutputStream oos = new ObjectOutputStream(fos);
            oos.writeObject(singleton);
            oos.flush();
        }
        //反序列化
        @SuppressWarnings("unchecked")
        private <T> T deserializable(String filename) throwsIOException,
                                                ClassNotFoundException {
            FileInputStream fis = new FileInputStream(filename);
            ObjectInputStream ois = new ObjectInputStream(fis);
            return (T) ois.readObject();
        }}

3). 运行测试: 啊偶~报错了,得到了两个不同的对象 从结果中可以看出,得到了两个不同的对象,一个Singleton@deb6432和一个Singleton@1b4fb997; 好吧,解决方案当然是有的,你只要在单例类里面加上下面这个方法:

private Object readResolve() {
    

在运行一遍测试代码,问题解决!

对于静态方法Elvis.getinstance()的所有调用,都会返回同一个对象引用,所以,永远不会创建其他的Elvis实例。

公有域方法的好处:

  • 可以在类的成员的声明很清楚地表明这类睡个Singleton,公有的静态域是final的,所以该与弄湿包含相同的对象引用。
  • 灵活性:在不改变API的前提下,我们可以改变该类的单例想法。

4). 注意: 想让该类编程可序列化的。仅仅在类声明上添加implements Serializable是不够的,为了维护并保证Singleton ,必须声明所有的实例域都是瞬时(transient)的。

4. 利用枚举来强化Singleton(最佳方案)

利用单元素的枚举来实现单例(Singleton),绝对防止多次实例化,上面的问题在这里都不会出现,实现代码如下:

public enum SingletonEnum {
        INSTANCE;

        private String filed01;

        public String getFiled01() {
            return filed01;
        }
        public void setFiled01(String filed01) {
            this.filed01 = filed01;
        }}

使用的时候跟我们常用的单例方式也十分相似:

SingletonEnum.INSTANCE.setFiled01("123");
System.out.println(SingletonEnum.INSTANCE.getFiled01());

5. 参考文献

www.jianshu.com/p/286231f0f…

horizonliu.github.io/2019/01/08/…

关注公众号“程序员面试之道”

回复“面试”获取面试一整套大礼包!!!

本公众号分享自己从程序员小白到经历春招秋招斩获10几个offer的面试笔试经验,其中包括【Java】、【操作系统】、【计算机网络】、【设计模式】、【数据结构与算法】、【大厂面经】、【数据库】期待你加入!!!

1.计算机网络----三次握手四次挥手

2.梦想成真-----项目自我介绍

3.你们要的设计模式来了

4.震惊!来看《这份程序员面试手册》!!!

5.一字一句教你面试“个人简介”

6.接近30场面试分享

7.你们要的免费书来了