enum一定是java enum 线程安全全的吗

你所不知的 java 枚举
java 中的枚举在实现上非常简单,以下就是一个枚举的例子:
但是如果反编译 FruitEnum.class,会发现编译器在背后默默的做了大量的工作,以下是反编译结果:
下面根据反编译结果说明枚举的几个特性。
枚举的实际类型
说明枚举是类,并且是用 final 修饰的类,意味着枚举不能再被继承扩展。而我们声明枚举类时使用的 enum 只是一个关键字。
其次,所有的枚举都继承了一个基类 Enum ,该基类为枚举提供了一些通用方法。
枚举的构造函数
在 FruitEnum 中,我们并没有定义构造函数,但在反编译的代码中,我们发现编译器自动帮我们添加了以下私有构造函数:
该私有构造函数只是简单调用了父类 Enum 的构造函数:
实际上,编译器只允许私有的枚举类构造函数,并且显示定义的枚举构造函数经编译器后,都会被添加 name 和 ordinal 2个参数,用以调用父类 Enum 的构造函数。
再看反编译后的 FruitEnum 中的成员变量:
其中 FruitEnum APPLE对应我们定义的枚举常量 APPLE,而 FruitEnum $VALUES[]则是容纳所有枚举常量的数组。
这两个变量初始化操作如下:
可知,每个枚举常量都是定义它的类的对象,各枚举对象都是用 public static final修饰的,这也是枚举对象被称为枚举常量的原因。
其次,FruitEnum APPLE和 FruitEnum $VALUES[]都是在静态代码块中进行初始化,因此在 JVM 记载完成枚举类后,各枚举常量都已被
创建完毕。由于枚举类中只允许出现私有构造函数,固无法再实例化新的枚举对象。
枚举类实例化
无法通过构造函数这种一般手段实例化枚举类,那么通过反射呢?
先尝试以下形式:
抛出了以下错误:
因为 Class.newInstance 内部是通过目标Class的 无参构造函数创建实例的,而上文已经说了 FruitEnum 的实际构造函数是:
固会找不到对应的构造函数,抛出异常。
换种反射方式,先获取正确的构造函数:
成功获取了 FruitEnum 的构造函数,然而还是抛出了异常:
在 JVM 层面禁止了通过反射构造枚举实例的行为。
然而除了反射外,还可以通过 Object.clone() 方法克隆一个已存在的对象。但是这种方式也是不行的,看 Enum 类中的 clone()方法:
Enum 类重写了 Obejct 的 clone()方法,只要尝试通过 clone()方法构造枚举对象,都会抛出异常,并且该方法被设置为不能再重写。
从编译器、JVM,再到 java 内部设计,层层把关,封死了实例化枚举类的这一企图。因此有一种作法是通过枚举的形式实现单例,下面给个示例:
线程安全枚举常量都是 static 类型的,在枚举类加载完成后,会进行枚举常量的初始化,之后枚举类无法再实例化和修改。java 的类加载、初始化过程是线程安全的,因此创建一个 enum 是线程安全的。
觉得本文有帮助?请分享给更多人
关注「猿助猿」成就顶级开发
技术交流QQ群:
责任编辑:
声明:本文由入驻搜狐号的作者撰写,除搜狐官方账号外,观点仅代表作者本人,不代表搜狐立场。
猿助猿(www.dadio.xyz),开发者进阶互助圈,全球领先的在线编程测评平台!
猿助猿(www.dadio.xyz),开发者进阶互助圈,全球领先的在线编程测评平台!
今日搜狐热点评论-2684&
& 通过一段时间的项目实践,发现java中的枚举与.net中的枚举有很大的差别,初期造成了我对java中的枚举一些错误理解及部分有缺陷的应用,其实追其原因还是因为我会习惯性的认为java的枚举在作用以及定义上与.net应该是差不多的,毕竟两者都是高级语言,语言上也有很多相似之处。这就是老师傅常说的新手好教,老兵不好教的原因,新手脑子一片空白不会有任何干扰,老兵总会以自己曾经的某些经验与新知识做对比。
& & 习惯性观点一:枚举的定义应该与.net相同,比如在.net中我们可以这样定义枚举。
public enum EItemDataType
& 但java中并不能如此潇洒的书写枚举,可能需要类似这样写:
public enum EItemDataType {
Real(1),Service(2);
private int
private EItemDataType(int value) {
this.value =
public int getValue() {
public static EItemDataType valueOf(int value) {
switch (value) {
return EItemDataType.R
return EItemDataType.S
return null;
& 发现.net要比java简单的多,注意几个方法:
&valueOf的方法:看作用是为了根据一个枚举的数值来得到枚举,这个功能很常见,但在.net中就不需要这样麻烦了,可以直接将数据强转成枚举,比如:
var itemType=(EItemDataType)1;
getValue的方式,明显是需要将一个枚举转换成它所对应的值,.net中也不需要调用方法来取值,也可以强转,比如:
var itemTypeValue=(int)EItemDataType.R
& 私有构造函数,我们可以传多少参数,比如常见的我们需要显示这个枚举值对应的中文描述,在java中我们只需要在构造函数中增加一个name参数就可以了,但在.net中因为没有这货不能这样做,但可以通过& Atrribute来完成。
public enum EItemDataType
[Description("实物")]
[Description("服务")]
& 习惯性观点二:因为.net的枚举是个值类型,所以我理所当然的会认为java的枚举也是一个值类型。之前对.net的理解就是将一些数值以更加可读性的方式体现在程序中,比如订单状态,订单类型等等,比如:
//枚举值可读性更强
if(orderInfo.orderStatus.equals(EOrderStatus.Shipped)){
//do something
//一般不这样写,0可读性不强
if(orderInfo.orderStatus==0){
//do something
&枚举类型的自说明:&
编译后的文件中找到了EItemDataType.class这个文件,这说明java的枚举其实和普通的类是一样的,既然是一个类,那么肯定不是值类型了,下图中的引用类型中包含class type。
&&&&&&& 编译之后所对应的字节码到底是什么样的:
public final class EItemDataType extends java.lang.Enum&EItemDataType& {
public static final EItemDataType R
public static final EItemDataType S
static {};
// class EItemDataType
// String Real
6: iconst_0
7: iconst_1
8: invokespecial #16
// Method "&init&":(Ljava/lang/SII)V
11: putstatic
// Field Real:LEItemDataT
// class EItemDataType
// String Service
20: iconst_1
21: iconst_2
22: invokespecial #16
// Method "&init&":(Ljava/lang/SII)V
25: putstatic
// Field Service:LEItemDataT
28: iconst_2
29: anewarray
// class EItemDataType
33: iconst_0
34: getstatic
// Field Real:LEItemDataT
37: aastore
39: iconst_1
40: getstatic
// Field Service:LEItemDataT
43: aastore
44: putstatic
// Field ENUM$VALUES:[LEItemDataT
47: return
public int getValue();
0: aload_0
1: getfield
// Field value:I
4: ireturn
public static EItemDataType valueOf(int);
0: iload_0
1: tableswitch
{ // 1 to 2
default: 32
24: getstatic
// Field Real:LEItemDataT
27: areturn
28: getstatic
// Field Service:LEItemDataT
31: areturn
32: aconst_null
33: areturn
public static EItemDataType[] values();
0: getstatic
// Field ENUM$VALUES:[LEItemDataT
4: astore_0
5: iconst_0
6: aload_0
7: arraylength
9: istore_1
10: anewarray
// class EItemDataType
14: astore_2
15: iconst_0
16: iload_1
17: invokestatic
// Method java/lang/System.arraycopy:(Ljava/lang/OILjava/lang/OII)V
20: aload_2
21: areturn
public static EItemDataType valueOf(java.lang.String);
// class EItemDataType
2: aload_0
3: invokestatic
// Method java/lang/Enum.valueOf:(Ljava/lang/CLjava/lang/S)Ljava/lang/E
6: checkcast
// class EItemDataType
9: areturn
& 是个final类型的,不允许继承自其它类型
& 继承了java.lang.Enum类,更说明这个枚举就是个class
public final class EItemDataType extends java.lang.Enum&EItemDataType& {
& 所有的枚举值都被定义成静态值了,且以常量形式存在
public static final EItemDataType R
& 再看下一个特殊的方法,由于枚举继承了java.lang.Enum这个类,那么它自然拥有一些实用的方法:
public static EItemDataType valueOf(java.lang.String);
&&&& 这是个字符串参数类型的方法,和我上面定义的valueOf(int value)很像,其目的都是根据一定的条件获取枚举值,只不过方式不同而已,前者是自带的根据枚举值toString的结果来反向获取枚举值,与toString的对应,比如:EItemDataType.Real.toString()它等于&Real&,再调用EItemDataType.valueOf("Reail"),它等于EItemDataType.Real这个值。自定义的valueOf(int value)方式个人感觉并不太好,因为容易与自带的那个方法冲突,最好是改个名称,比如value什么。
&& 最后我们再来看下枚举所能实现的奇葩功能:单例(之前学习.net时写的日记)。刚开始看到java的单例可以通过枚举实现时,我都惊呆了,最大的反应是枚举是个存储值的怎么和单例有关系?单例不是class的事吗?其实通过上面的理解,枚举就是个类,那么再想想单例就不会有什么疑问了,把它当成一个普通类不就好了,我们看一个简单的计数的例子:按照上面字节码的结构,这个INSTANCE2会被定义成一个静态变量,正是利用静态变量唯一性的特性来实现了单例,而且是线程安全的。&
public enum SafeSingleton implements Serializable {
INSTANCE2;
public void addCount(int i)
this.count+=i;
public void printCount()
System.out.println(this.count);
& 下面这段程序会输出5050&&&&
for(int i=1;i&=100;i++){
SafeSingleton.INSTANCE2.addCount(i);
SafeSingleton.INSTANCE2.printCount();
& java中的枚举是一个比较特殊的数据类型,除了具备值存储的能力还拥有class特性,作用范围相比.net要大,但实现更加复杂些。
阅读(...) 评论()java笔记(10)
设计模式(1)
Enum是否支持序列化,以及底层的实现原理
Enum类型的策略枚举这种设计模式如何实现和优缺点
Enum支持序列化吗
支持,在Java中,只要一个类实现了java.io.Serializable接口,那么它就可以被序列化。
Enum底层实现
当我们使用enmu来定义一个枚举类型的时候,编译器会自动帮我们创建一个final类型的类继承Enum类,所以枚举类型不能被继承,这个类中有几个属性和方法都是static类型的,因为static类型的属性会在类被加载之后被初始化,当一个Java类第一次被真正使用到的时候静态资源被初始化、Java类的加载和初始化过程都是线程安全的。所以,创建一个enum类型是线程安全的。
一旦实现了Serializable接口之后,就不再是单例得了,因为,每次调用 readObject()方法返回的都是一个新创建出来的对象,有一种解决办法就是使用readResolve()方法来避免此事发生。但是,为了保证枚举类型像Java规范中所说的那样,每一个枚举类型极其定义的枚举变量在JVM中都是唯一的,在枚举类型的序列化和反序列化上,Java做了特殊的规定。
在序列化的时候Java仅仅是将枚举对象的name属性输出到结果中,反序列化的时候则是通过java.lang.Enum的valueOf方法来根据名字查找枚举对象。同时,编译器是不允许任何对这种序列化机制的定制的,因此禁用了writeObject、readObject、readObjectNoData、writeReplace和readResolve等方法。原文参考
设计模式之策略枚举
策略模式定义
定义一系列的算法,把每一个算法封装起来, 并且使它们可相互替换。本模式使得算法可独立于使用它的客户而变化。也称为政策模式(Policy)。(Definea family of algorithms,encapsulate each one, andmake them interchangeable. Strategy lets the algorithmvary independently from clients that use it. )
从以上官方定义分析到策略模式具有三个角色分别是:
- 环境类(Context):(如上图中的Context,根据不同的使用环境调用不同的策略接口)
- 抽象策略类(Strategy):(策略接口,如上图中的Strategy)
- 具体策略类(ConcreteStrategy):(实现策略接口的类:如上图中的ConcreteStrategyA,B,C)
以加减法为例来划分一下其中的角色,比如 加法:1+1=2,减法1-1=0。
可以抽象出来为除了加减的符号不同其他都是一样的:两个参数,一操作符号,和返回值。
因此可以定义个接口
参考源码作者:
* 定义策略接口(一组策略)
* yanwenfei
public interface IStrategy {
public int exec(int a, int b);
接下来实现加减的两个具体算法
* 策略A加法
* yanwenfei
public class StrategyADD implements IStrategy {
public int exec(int a, int b) {
return a+b;
* 减法策略
* yanwenfei
public class StrategySub implements IStrategy {
public int exec(int a, int b) {
return a -
策略封装,而高层不用知道具体是哪种策略,只需要调用抽象策略的方法即可
* 策略环境即封装
* yanwenfei
public class Calculator {
private IS
public Calculator(IStrategy strategy) {
this.strategy =
public int exec(int a, int b){
return strategy.exec(a, b);
public class TestMain {
public static void main(String[] args) {
Calculator calculator = null;
IStrategy streadd = new StrategyADD();
calculator = new Calculator(streadd);
int add = calculator.exec(20, 30);
System.out.println("20 + 30 = " + add);
IStrategy stresub = new StrategySub();
calculator = new Calculator(stresub);
int sub = calculator.exec(20, 30);
System.out.println("20 - 30 = " + sub);
以上为策略模式传统的实现方式,肯定很多人能看出来这个策略模式有很多缺点,虽然便于扩展,但是每一个策略都是一个类,这个先不说,下来看一下更简略的策略枚举。
* 策略枚举
* yanwenfei
public enum Calculator {
ADD("+") {
public int exec(int a, int b) {
return a+b;
SUB("-") {
public int exec(int a, int b) {
return a-b;
public abstract int exec(int a, int b);
private String value = "";
private Calculator(String value) {
this.value =
public String getValue() {
public class TestMain {
public static void main(String[] args) {
int add = Calculator.ADD.exec(10, 30);
System.out.println("10 + 30 = "+add);
int sub = Calculator.SUB.exec(10, 30);
System.out.println("10 - 30 = "+sub);
策略模式优缺点
策略模式优点
- 算法可以自由切换(高层屏蔽算法,角色自由切换)
- 避免使用多重条件判断(如果算法过多就会出现很多种相同的判断,很难维护)
- 扩展性好(可自由添加取消算法 而不影响整个功能)
策略模式缺点
- 策略类数量增多(每一个策略类复用性很小,如果需要增加算法,就只能新增类)
- 所有的策略类都需要对外暴露(使用的人必须了解使用策略,这个就需要其它模式来补充,比如工厂模式、代理模式)
my github:
没有什么岁月静好,只是有人把黑暗挡在你看不到的地方主题信息(必填)
主题描述(最多限制在50个字符)
申请人信息(必填)
申请信息已提交审核,请注意查收邮件,我们会尽快给您反馈。
如有疑问,请联系
CSDN &《程序员》研发主编,投稿&纠错等事宜请致邮
你只管努力,剩下的交给时光!
如今的编程是一场程序员和上帝的竞赛,程序员要开发出更大更好、傻瓜都会用到软件。而上帝在努力创造出更大更傻的傻瓜。目前为止,上帝是赢的。个人网站:www.xttblog.com。个人QQ群:、
个人大数据技术博客:https://www.iteblog.com
人生得意须尽欢,莫使金樽空对月。
分析枚举的线程安全性及序列化问题他的最新文章
他的热门文章
您举报文章:
举报原因:
原文地址:
原因补充:
(最多只允许输入30个字)}

我要回帖

更多关于 atomic一定是线程安全 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信