第三条:用私有构造或者枚举类强化Singleton属性

2020-11-30  本文已影响0人  Js_Gavin

Singleton(单例)是指仅仅被实例化一次的类,Singleton通常表示一个无状态的对象,或者本质上唯一的系统组件。

实现Singleton有两种常用的方式,这两种方式都要保证构造器的私有化,并导出共有的静态成员变量,以便客户端能够获取类唯一的实例,第一种方法:

public class SingletonDemo{
    public static final SingletonDemo SINGLETON_DEMO = new SingletonDemo();
    private SingletonDemo(){}
}

这种方式私有构造器仅被调用一次,用来实例化公有的静态域SINGLETON_DEMO,由于缺少公用的,或者受保护的构造器,所以保证了SINGLETON_DEMO的全局唯一性,一旦SINGLETON_DEMO被实例化,只会存在一个SINGLETON_DEMO实例。但是可以通过借助反射的方式,调用私有构造器。举个例子:

public class Main{
    public static void main(String[] args){
        SingletonDemo singletonDemo = SingletonDemo.SINGLETON_DEMO;
        SingletonDemo singletonDemo2 = SingletonDemo.SINGLETON_DEMO;
        Class clazz = SingletonDemo.class;
        SingletonDemo newSingletonDemo = null;
        try {
            Constructor constructor = clazz.getDeclaredConstructor();
            constructor.setAccessible(true);
            newSingletonDemo = (SingletonDemo) constructor.newInstance();
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println(singletonDemo == singletonDemo2);
        System.out.println(singletonDemo == newSingletonDemo);
        System.out.println(singletonDemo2 == newSingletonDemo);
    }
}

如果需要抵御这种攻击,可以在构造器中添加如下代码,使其在第二次调用私有构造器的时候抛出异常:

public class SingletonDemo{
    public static final SingletonDemo SINGLETON_DEMO = new SingletonDemo();
    private SingletonDemo(){
       if(SINGLETON_DEMO != null){
            throw new AssertionError();
        }
    }
}

实现Singleton的第二中方法时,共有的成员是一个静态方法:

public class SingletonDemo{
    private static final SingletonDemo SINGLETON_DEMO = new SingletonDemo();

    private SingletonDemo() {
        if(SINGLETON_DEMO != null){
            throw new AssertionError();
        }
    }

    public static SingletonDemo getInstance() {
        return SINGLETON_DEMO;
    }
}

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

公有域的优势在于,可以很清楚的表示这是一个Singleton类,第二个优势在于,它更简单。

公有方法的优势之一在于,提供了灵活性,在不改变API情况下,可以任意改变该类是否为Singleton的想法,也可以很容易的修改为为每个调用该方法的线程返回一个唯一的实例。

如果要将Singleton类变成可序列化的,仅仅在类上声明 implements Serializable是不够的,因为每次反序列化一个对象的时候,readObject()方法都会创建一个新的实例,所以为了保证Singleton的单一性,必须声明所有实例域都是瞬时(transient),并在类中添加一个readResolve方法(不是重写也不是实现),反序列化之后,新建对象上的readResolve()方法就会调用,然后,该方法返回的对象引用 将被返回取代新的对象。举个栗子:

public class SingletonDemo implements Serializable {
    private static final SingletonDemo SINGLETON_DEMO = new SingletonDemo();
    private transient String name;
    private transient Integer age;
    private SingletonDemo() {
        if (SINGLETON_DEMO != null) {
            throw new AssertionError();
        }
    }
    public static SingletonDemo getInstance() {
        return SINGLETON_DEMO;
    }
    // 声明readResolve()方法
    private Object readResolve() {
        System.out.println("readResolve");
        return SINGLETON_DEMO;
    }
}
public class Main{
    public static void main(String[] args) throws Exception {
        ByteArrayOutputStream by = new ByteArrayOutputStream();

        SingletonDemo2 s1 = SingletonDemo2.getInstance();

        ObjectOutputStream out = new ObjectOutputStream(by);
        out.writeObject(s1);
        out.close();

        ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(by.toByteArray()));
        SingletonDemo2 s2 = (SingletonDemo2) in.readObject();
        in.close();

        System.out.println(s1 == s2);

    }
}
上一篇下一篇

猜你喜欢

热点阅读