写点什么

Java 程序经验小结:用私有构造器或者枚举类型强化 Singleton 属性

发布于: 2021 年 01 月 13 日
Java 程序经验小结:用私有构造器或者枚举类型强化Singleton属性

1、写在开头

Singleton 指仅仅被实例化一的类,通常用于代表那些本质上唯一的系统组件,比如窗口管理器或者文件系统。

使类成为 Singleton 会使它的客户端测试变得困难,因为无法给 Singleton 替换模拟实现,除非它实现一个充当其类型的接口。

实现 Singleton 共有 3 种方法,在 Java 1.5 发行版之前实现有两种;在 Java 1.5 发行版之后实现 Singleton 还有第三种方法,下面进行依次介绍。


2、第一种:公有静态类是个 final 域

构造器保持为私有,导出公有静态成员,客户端可以访问该类的唯一实例。



public class Elvis {
public static final Elvis INSTANCE = new Elvis();
//构造器保持为私有,导出公有静态成员,客户端可以访问该类的唯一实例 private Elvis(){ //... }
public void leaveTheBuilding(){ //... }}
复制代码


但要注意一点:享有的客户端可以借助 AccessiableObject.setAccessible 方法,通过反射机制调用私有构造器。如果需要抵御这种攻击,可以修改构造器,代码里面做个检测判断,在被要求创建第二个实例时抛出异常。


3、第二种:公有成员是个静态工厂方法

实现 Singleton 的第二种方法,公有的成员是个静态工厂方法:



public class Elvis2 { // 私有成员 private static final Elvis2 INSTANCE = new Elvis2();
// 静态方法公有成员 public static Elvis2 getInstance(){ return INSTANCE; }
//构造器保持为私有,导出公有静态成员,客户端可以访问该类的唯一实例 private Elvis2(){ //... }
public void leaveTheBuilding(){ //... }}
复制代码


公有域方法的好处是,组成类成员的声明,很清楚的表明这个类是一个 Singleton。公有的静态域是 final 的, 所以该域总是包含相同的对象引用。

但公有域方法在性能上不再有任何优势了,因为现代的 JVM 实现几乎都能够将静态工厂方法的调用内联化(Inline Method)

另外,工厂方法有 2 个优势:

  • 在不改变 API 的前提下,我们可以改变此类是否应该为 Singleton 的想法。工厂方法返回该类的唯一实例,但会容易被修改,比如改为每个调用该方法的线程返回一个唯一的实例。

  • 第二个优势,与泛型有关。为了让 Singleton 类变成可序列化的(Serializable),仅仅在对象声明中加上“implements Serializable” 是不够的。为了维护并保证 Singleton,必须声明所有实例域都是瞬时的(transient),并提供一个 readResolve 方法。否则每次反序列化都会创建一个新实例。


  // readResolve method to preserve singleton property  private Object readObject(){    // Return the one true Elvis and let the garbage collector take care of the Elvis impersonator    return INSTANCE;  }
复制代码


4、第三种:包含单个元素的枚举类型

第三种实现 Singleton 的方法是编写一个包含单个元素的枚举类型:



public class SingletonCase6 { enum SingletonEnum { //创建一个枚举对象,该对象天生为单例 INSTANCE; private SingletonCase6 singleton;
//私有化枚举的构造函数 private SingletonEnum() { System.out.println("-----1 init constructor-----"); singleton = new SingletonCase6(); }
public SingletonCase6 getSingleton() { System.out.println("-----2 return singleton-----"); return singleton; } }
private SingletonCase6() { }
public static SingletonCase6 getInstance(){ System.out.println("-----3 getInstance -----"); return SingletonEnum.INSTANCE.getSingleton(); }
public static void main(String[] args) { getInstance(); }}
复制代码


输出结果:



-----3 getInstance ----------1 init constructor----------2 return singleton-----
复制代码


这种方法在功能上跟公有域方法相近,但它更加简洁,无偿的提供了序列化机制,绝对防止多次实例化,即使是面对复杂的序列化或者反射攻击的时候。

5、总结

上面的第三种方法元素的枚举类型已经成为实现 Singleton 最佳方法

单例模式实际上有 5 种(懒汉模式/饿汉模式/双锁检测模式/内部类/枚举类),可以参考相关的单例模式文章:

  1. 设计模式学习(五):单例模式 (上)

  2. 设计模式学习(五):单例模式 (下)


发布于: 2021 年 01 月 13 日阅读数: 18
用户头像

Diligence is the mother of success. 2018.03.28 加入

公众号:后台技术汇 笔者主要从事Java后台开发,喜欢技术交流与分享,保持饥渴,一起进步!

评论

发布
暂无评论
Java 程序经验小结:用私有构造器或者枚举类型强化Singleton属性