Effective Java —— 用私有构造器或枚举类型强化单例属性


本文参考

本篇文章参考自《Effective Java》第三版第三条"Enforce the singleton property with a private constructor or an enum type"

原文的不足之处

原文给的示例代码比较简单,提供的是一种饿汉式的单例模式

// Singleton with public final field
public class Elvis {
    public static final Elvis INSTANCE = new Elvis();
    private Elvis() { ... }
    public void leaveTheBuilding() { ... }
}

我们也可以将INSTANCE实例的pulice改为private,通过另一个静态方法获取实例

// Singleton with static factory
public class Elvis {
    private static final Elvis INSTANCE = new Elvis();
    private Elvis() { ... }
    public static Elvis getInstance() { return INSTANCE; }
    public void leaveTheBuilding() { ... }
}

构造器的private关键字修饰使得无法通过new关键字创建其它实例,final关键字的修饰使得INSTANCE总是包含相同的对象引用,这两种写法都能保证全局下仅有一个Elvis类的实例化对象

因为饿汉模式下,全局的单例实例在类装载时就进行构建,可能由于初始化的太早,会造成资源的浪费,所以有另一种懒汉式的单例模式,但原文中未做介绍,因为它在多线程下存在实例重复构建的问题,若要解决这个问题,会加大文章篇幅,而且也不是第三条建议的重点,更多关于懒汉式的单例模式可以参考这篇文章:

https://www.jianshu.com/p/eb30a388c5fc

 

the best way to implement a singleton —— a single-element enum type

Java的枚举类型是一个特殊的class,并继承了Enum类,若Singleton必须扩展一个超类,而不是扩展Enum 时,则不宜使用如下方法

public class Elvis {
  private Elvis() {
  }

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

  /**
   *
注意声明为private
   */
  private enum
Singleton {
    INSTANCE;
    private Elvis singleton;
    /**
     *
仅执行一次
     */
    Singleton() {
      singleton = new Elvis();
    }
    public Elvis getInstance() {
      return singleton;
    }
  }
}

因为Enum类实现了Serializable接口,所以为INSTANCE自动提供了序列化机制,并且编译器会为枚举类型的实例自动添加 public static final 修饰,所以枚举类型的无参构造方法仅会被执行一次

原文也指出这种方式能够应对复杂的序列化或者反射攻击的情形

provides the serialization machinery for free, and provides an ironclad guarantee against multiple instantiation, even in the face of sophisticated serialization or reflection attacks.