用私有构造器或枚举来强化单例属性

Posted by BY Blog on January 8, 2019

单例模式

我们知道,单例模式的实现,主要由三个步骤组成,即:

  1. 声明一个静态私有的成员变量;
  2. 私有化构造函数;
  3. 声明一个静态共有的函数,函数返回该对象的实例。

一个典型的单例类定义如下:

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

    private Sigleton() {}

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

但是,在java中,享有特权的客户端可以通过反射机制 AccessibleObject.setAccessible来调用私有构造器,所以为了防止出现这种问题,可以将私有构造器进行改造,在调用私有构造器的时候抛出异常即可,如下:

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

但即使如此,仍然有一种情况会出现多个实例:在序列化这个对象的实例后,再对该实例进行反序列化,会得到该对象的一个新的实例。

下面做一下测试:

1. 将Sigleton类实现Serializable接口
public class Sigleton implements Serializable {
    ......
}
2. 进行序列化反序列化测试
public class SerializableTest {

    public static void main(String[] args) throws Exception{
        // 序列化Singleton实例
        serializable(Sigleton.getInstance(), "test");
        // 反序列化该实例
        Sigleton singleton = deserializable("test");
        // 检查序列化前和序列化后的实例是否相等
        if (singleton != Sigleton.getInstance()) {
            System.out.println("singleton: " + singleton.toString());
            System.out.println("Singleton.getInstance: " + Sigleton.getInstance());
            System.out.println("two instances are different\n");
        } else {
            System.out.println("two instances are the same\n");
        }
    }

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

运行该程序,得到如下结果:

singleton: com.effective.java.sigleton.Sigleton@1d81eb93
Singleton.getInstance: com.effective.java.sigleton.Sigleton@5e481248
two instances are different

可以看出序列化前和序列化后得到的两个实例是不一样的。那么如何解决上述问题呢,一共有两种解决方式。

方法一:添加readResolve函数

在Sigleton单例类中添加readResolve函数

    private Object readResolve() {
        return INSTANCE;
    }

接下来再次运行SerializableTest 程序,得到结果:

two instances are the same
方法二:利用枚举来强化Singlton
public enum SingletonEnum {
    /**
     * 实例
     */
    INSTANCE;

    private String field;

    public String getField() {
        return field;
    }

    public void setField(String field) {
        this.field = field;
    }
}


// 调用方法
SingletonEnum.INSTANCE.setField("123");
System.out.println(SingletonEnum.INSTANCE.getField());

这种方法在功能上与共有域方法相近,但是更加简洁,无偿的提供了序列化机制,绝对防止多次实例化,即使在面对复杂的序列化或者反射调用时。虽然这种方法还没有被广泛使用,但是单元素的枚举类型已经成为实现Singleton 的最佳方法。

github完整代码

https://github.com/HorizonLiu/effective.java.learning