设计模式【1.2】-- 枚举式单例有那么好用么?
1. 单例是什么?
单例模式:是一种创建型设计模式,目的是保证全局一个类只有一个实例对象,分为懒汉式和饿汉式。所谓懒汉式,类似于懒加载,需要的时候才会触发初始化实例对象。而饿汉式正好相反,项目启动,类加载的时候,就会创建初始化单例对象。
前面说过单例模式以及如何破坏单例模式,我们一般情况尽可能阻止单例模式被破坏,于是各种序列化,反射,以及克隆的手段,我们都需要考虑进来,最终的代码如下:
前面提过破坏序列化的四种方式:
- 没有将构造器私有化,可以直接调用。
- 反射调用构造器
- 实现了
cloneable
接口 - 序列化与反序列化
2. 枚举的单例可以被破坏么?
但是突然想到一个问题,一般都说枚举的方式实现单例比较好,较为推荐。真的是这样么?这样真的是安全的么?
那我们就试试,看看各种手段,能不能破坏它的单例。首先我们来写一个单例枚举类:
在命令行执行以下的命令看上面的枚举类编译之后到底是什么东西?
可以看出,实际上,编译后的代码是继承于Enum
类的,并且是泛型。用final
修饰,其实也是类,那就是不可以被继承原因。而且INSTANCE
也是final
修饰的,也是不可变的。但是这样看,上面的都是public
方法。那构造方法呢?没有被重写成为private
么?
要是没有重写的话,那就很容易破坏单例啊!我们使用javap -p SingletonEnum
看看结果:

可以看出确实构造函数已经被私有化,那么外部就不能直接调用到构造方法了。那其他方法呢?我们试试放射调用构造器:
执行结果如下:
咦,怎么回事?反射失败了???

看起来报错是getDeclaredConstructor()
失败了,那我们看看到底有哪些构造器:
执行结果如下,发现只有一个构造器,里面参数是String
和int
,所以啊,反射调用无参数构造器肯定也是如此。
毕竟它是继承于Enum
的,那我猜想它大概也只有这个方法,验证以下,打开源码:
可以看出,这里面只有两个属性:name
和ordinal
,构造器被重写了,正是String
和int
,验证了我们的猜想,也就是没有办法使用无参数构造器来构造出破坏单例的对象。那要是我们使用有参数构造呢?试试!!!
结果呢?还是一样的报错,这是什么东东?
看起来意思是不能反射创建enum对象,啥?这错误一看,就是Constructor.newInstance()
417行抛出来的,我们看看:
原来反射的源代码中,枚举类型的已经被限制了,一旦调用就会抛出异常,那这条路走不通了,也就证明了反射无法破坏枚举的单例。new
对象更是行不通了。
那clone
呢?打开Enum
的源码我们里面就断了这个念头,这里面的clone()
方法,已经被final
修饰了,不能被子类重写,一调用就抛出异常。所以clone
这条路也不可能破坏枚举的单例模式。
那序列化呢?如果我们序列化之后,再反序列化,会出现什么情况?
上面的代码执行之后,结果如下:
说明序列化反序列化回来之后,其实是同一个对象!!!所以无法破坏单例模式。为什么呢?我们来分析一下源码!!!
先看看序列化的时候,实际上调用的是ObjectOutputStream.writeObject(Object obj)
writerObject()Object obj
方法里面调用了writeObject0(obj,false)
,writeObject0(obj,false)
里面看到枚举类型的序列化写入:
writeEnum(Enum<?>)
里面是怎么序列化的呢?
看起来序列化的时候,是用名字写入序列化流中,那反序列化的时候呢?是怎么操作的呢?
里面调用的是另外一个readObject()
方法,readObject()
方法其实是调用了readObject0(type,false)
。
看到反序列化的时候,枚举类型的时候,是怎么实现的呢?里面有一个readEnum()
:
我们来看看readEnum()
,里面其实里面是先读取了名字name
,再通过名字Enum.valueOf()
获取枚举。
所以上面没有使用反射,还是获取了之前的对象,综上所述,枚举的序列化和反序列化并不会影响单例模式。
3. 总结一下
经过上面一顿分析,枚举不可以直接调用构造函数,不可以反射破坏单例模式,因为内部实现阻止了,实现clone
接口也不可以,这个方法已经设置为final
。序列化和反序列化的时候,内部没有使用反射去实现,而是查找之前的对象,直接返回,所以还是同一个对象。
这样一来,怪不得《effective java》里面推荐这个写法,既简洁,还能够防止各种破坏,还有不用的理由么?
【作者简介】:
秦怀,公众号【秦怀杂货店】作者,技术之路不在一时,山高水长,纵使缓慢,驰而不息。这个世界希望一切都很快,更快,但是我希望自己能走好每一步,写好每一篇文章,期待和你们一起交流。
此文章仅代表自己(本菜鸟)学习积累记录,或者学习笔记,如有侵权,请联系作者核实删除。人无完人,文章也一样,文笔稚嫩,在下不才,勿喷,如果有错误之处,还望指出,感激不尽~
__EOF__

本文链接:https://www.cnblogs.com/Damaer/p/14194319.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角【推荐】一下。您的鼓励是博主的最大动力!
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· .NET Core 托管堆内存泄露/CPU异常的常见思路
· PostgreSQL 和 SQL Server 在统计信息维护中的关键差异
· C++代码改造为UTF-8编码问题的总结
· DeepSeek 解答了困扰我五年的技术问题
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· 清华大学推出第四讲使用 DeepSeek + DeepResearch 让科研像聊天一样简单!
· 实操Deepseek接入个人知识库
· CSnakes vs Python.NET:高效嵌入与灵活互通的跨语言方案对比
· Plotly.NET 一个为 .NET 打造的强大开源交互式图表库