为什么要用枚举实现Singleton--java

为什么要用枚举实现Singleton--java

转载需注明出处

  • 理由一:无需再考虑可序列化的情况

      《effective java》第77条:对于实例控制,枚举类型优先于readResolve

      说到readResolve,有的人可能会不甚清楚其作用,简单来说,readResolve的作用是这样的:readResolve特性允许你用readObject创建的实例代替另一个实例。
      对于一个正在被反序列化的对象,如果他的类定义了一个readResolve方法,并具备正确的声明,那么在反序列化之后,新建对象上的readResolve方法就会被调用。然后,该方法返回的对象引用将会被返回,取代新建的对象。

      《effective java》中 第三条提到,如果在这个类的声明中加上了‘inplments Serializable’的字样,它就不再是一个Singleton。无论该类使用了默认的序列化形式,还是自定义的序列化形式,都没有关系;也跟它是否提供了显示的readObject方法无关。任何一个readObject方法,不管是显示的还是默认的,它都会返回一个新建的实例。这个新建的实例不同于该类初始化时创建的实例。这也是为什么要提供readResolve方法的原因。

    《effective java》第3条中:
      To make a singleton class that is implemented using either of the previous approaches serializable (Chapter 11), it is not sufficient merely to add implements Serializable to its declaration. To maintain the singleton guarantee, you have to declare all instance fields transient and provide a readResolve method (Item 77). Otherwise, each time a serialized instance is deserialized, a new instance will be created, leading, in the case of our example, to spurious Elvis sightings. To prevent this, add this readResolve method to the Elvis class:
    // readResolve method to preserve singleton property
    private Object readResolve() {
    // Return the one true Elvis and let the garbage collector
    // take care of the Elvis impersonator.
    return INSTANCE;
    }
      由于中文版中概念稍有混淆,特意去查阅英文原文。大体上的意思如下:
      为了使之前的方法实现的Singleton类可序列化,仅仅在声明中加上"implements Serializable"是不够的。为了保持Singleton,你需要声明所有实例字段为transient并提供readResolve方法。否则,每次一个序列化的实例被反序列化时,都会创建一个新的实例。比如在我们的例子中,会导致假冒的Elvis情况。为防止此种情况,要在Elvis类中加入下面这个readResolve方法:
    看到这里应该明白,对于一个需要序列化的Singleton来说,我们需要手动为其添加readResolve方法,在某些情况下,这样做会尤为复杂。
      而用枚举来实现Singleton则完全不必考虑,因为jvm可以保证这一点。

  • 理由二:无需再考虑通过反射调用私有构造函数的情况

      《effective java》第三条中:享有特权的客户端可以借助AccessiableObject.setAccessible方法,通过反射机制调用私有构造器。如果需要抵御这种攻击,可以修改构造器,让他在被要求第二次创建第二个实例的时候抛出异常。

       先看一个例子



    package singleton;
    import java.lang.reflect.Constructor;
    import java.lang.reflect.InvocationTargetException;
    //AprilCal on 2016.4.23
    class Singleton//最普通的一种Singleton实现
    {
    private static Singleton INSTANCE=new Singleton();
    private Singleton()
    {
    System.out.println("私有构造函数被调用");
    }
    public static Singleton getInstance()
    {
    return INSTANCE;
    }
    }
    enum EnumSingleton2//枚举实现的Singleton
    {
    INSTANCE;
    private EnumSingleton2()
    {
    System.out.println("enum 私有构造函数被调用");
    }
    }
    public class SingletonTest
    {
    public static void main(String[] args)
    throws ClassNotFoundException, IllegalAccessException,
    IllegalArgumentException, InvocationTargetException,
    NoSuchMethodException, SecurityException, InstantiationException
    {
    Class <?> cls1 = Class.forName("singleton.Singleton");
    Class <?> cls2 = Class.forName("singleton.EnumSingleton2");
    //通过反射调用Singleton的构造函数
    Constructor <?> c0=cls1.getDeclaredConstructor();
    c0.setAccessible(true);
    Singleton s=(Singleton)c0.newInstance();
    //通过反射调用EnumSingleton的构造函数
    Constructor &lt?> c1=cls2.getDeclaredConstructor();
    c1.setAccessible(true);
    EnumSingleton es=(EnumSingleton)c1.newInstance();
    }
    }

这段代码的执行结果如下:

私有构造函数被调用
enum 私有构造函数被调用
私有构造函数被调用
Exception in thread "main" java.lang.NoSuchMethodException: singleton.EnumSingleton2.()
at java.lang.Class.getConstructor0(Unknown Source)
at java.lang.Class.getDeclaredConstructor(Unknown Source)
at singleton.SingletonTest.main(SingletonTest.java:46)
>
在普通的Singleton中,我们通过反射机制调用了其私有的构造函数,而在通过反射调用enmuSingleton的私有构造函数时,则直接抛出了异常。可见,在使用枚举实现Singleton时,jvm会替我们完成防止通过反射调用私有构造函数的工作。

  • 理由三:枚举实例创建是线程安全的,无需再考虑Double checked locking

    我们都知道,在延迟初始化的情况下,为了保证线程安全,通常在实现Singleton的时候使用Double checked locking,而在枚举的情况下,我们则可以完全不必考虑这些。
    关于Double checked locking实现Singleton详见http://www.cnblogs.com/techyc/p/3529983.html
    并且此连接中有处细节需要纠正一下,用到Double checked locking时,必须要用volatile修饰单例的实例,否则将毫无意义。
    至于为什么要用volatile修饰,我不说了,嘻嘻。

  • 总结

    总结起来使用枚举类型实现Singleton主要有以下三大优势:

    1:无需再考虑可序列化的情况

    2:无需再考虑通过反射调用私有构造函数的情况

    3:枚举实例创建是线程安全的

    最后,千言万语一个字,使用枚举实现单例情况会好的多,但不排除某些情况用特殊的方法实现单例也同样很高效。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值