笔记--实现单例模式的3种简单方式

实现单例模式的3种简单方式

我们知道,在面向对象里面,类和对象一般是一对多的关系。一个类可以被实例化为多个对象,但是在我们开发过程中,有些地方要求我们必须使用使用同一个对象,也就是我们多次实例化一个类,得到的是同一个对象,这便是设计模式中的单例模式。

前两种实现思路比较简单,就是将构造器私有化,暴露公有的静态成员。

方式一:公有的静态成员为实例

public class Student{

    //导出公用的静态成员
    public static final Student INSTANCE = new Student();

    //私有化构造器
    private Student(){};
}

因为我们使用final对INSTANCE进行修饰,因此它仅仅可以被实例化一次,保证全局唯一。

方式二:共有的静态成员为静态工厂方法

public class Student{

    private static final Student INSTANCE = new Student();

    //共有的静态工厂方法
    public static Student getInstance(){
        return INSTANCE;
    }
    //私有化构造器
    private Student(){};
}

相对于方式一,方式二具有如下优势:

  • 灵活可以被改为每个线程返回一个唯一实例

  • 可以编写泛型单例工厂

  • 可以使用Java8之后的方法引用 Student::getInstance

没有上述要求的,推荐方式一

两种方式都无法避免被攻击者使用反射机制来调用私有的构造方法。

解决方案是在构造器中在被第二次实例化时抛出异常

无论是使用方式一还是方式二,当我们的单例是需要变为可序列化的时候,不能仅仅是使用implements Serializable就感觉万事大吉了,否则在每次反序列化时都会创建新的实例,也将不再是单例模式了。要想继续保证单例,我们必须实现readResolve()方法,如下:

import java.io.Serializable;

public class Student implements Serializable{

    private static final Student INSTANCE = new Student();

    //共有的静态工厂方法
    public static Student getInstance(){
        return INSTANCE;
    }
    //私有化构造器
    private Student(){};

    private Object readResolve(){
        return INSTANCE;
    }
}

再进行反序列化时,会创建一个Student的新实例,这个实例并非是之前的INSTANCE实例。而对于正确实现了readResolve()方法的类,在反序列化完成之后,会将readResolve()方法返回的实例替换掉反序列化生成的新实例,取代因为反序列化而产生的新对象。指向新对象的引用不再保留,因此立即会变为被GC回收的对象。这样就保证了实例的唯一性。

方式三:声明一个包含单个元素的枚举类型

public enum Student{

    INSTANCE;

    public void otherMethod(){}
 
}

这种方式最简洁,提供序列化机制,在复杂序列化或者反射攻击时都可以防止多次实例化。当Student需要继承父类时,此方法不宜使用。

记录于2020年4月29日21:29:35 阅读《Effective Java》第3条

原文地址:https://www.cnblogs.com/erkye/p/12804941.html