java enum真的有价值吗?

本文探讨了Java中枚举(enum)的真实用途及其与常量的区别,揭示了enum最适合的状态机应用场景,并通过代码示例展示了enum如何提供更好的可读性和安全性。


 翻遍了公司大大小小的java项目,惊讶地发现,源码中居然从头到尾都没有出现过enum关键字,这使本人对java枚举产生了一些疑惑:java的enum真的有价值吗?

        关于这个问题,即使是问一些有4、5年以上java开发经验的前辈,他们也答不上来,也难怪,对于实干派,只要能解决问题用什么还不一样。其实不止是这些人,一般的java开发者都基本不去用enum的,所以网上关于这个话题的资料也少,而且大多资料感觉都讲不到点子上,而本人又是那种对理论很执着的人,反正就是头很铁,最后只好决定亲自研究一波。

        虽然我们很多人对enum都没有怎么研究过,但始终会有一些印象或认识的,就是它与我们平时所讲的常量很相似,即人们普遍对enum的认识停留在它与"public final static"修饰的属性基本一致,而在同一使用场景下,大多人会选择自己更熟悉或习惯的后者去编码。但enum的真正作用与常量真的可以划等号吗?或者说凡是用常量的地方都能用enum去替代吗?这个问题的解答需要我们对enum有一个更好的认识。


什么是enum?

        归根到底,enum都不是java中的一个不可或缺的功能,因为即使没有它java也已经存在很多年了,它最多算是饭后甜点,是一个小功能,一个能让你的代码变得优雅而清晰的小功能,我认为它的推出是对当年崛起的c#的一种反击吧。从本质上讲,enum只是由多个具名的数值所组成的集合,仅此而已。


enum能替代常量吗?

        答案是不能的,实际上enum与我们平时所讲的常量关系不大,一般情况下我们习惯将用"public final static"关键字修饰的基本类型或字符串类型称为常量,这决定了常量是公开并且程序在任何时候都能访问的,同时也是不可变的。实际上enum也具有这个特点,但不同的是,枚举常量的值是被限定为数值的,而常量的类型不受约束,换言之它可以是数值,也可以是字符串等。所以不难理解enum的作用并不等价于常量,我们最多只能说enum与"public final static Number"很相似

        常量的普遍使用场景是用于存储配置文件信息。我们往往喜欢将多个常量放到统一的编译单元中,最典型的用法就是专门设一个Config类来存放程序运行时所加载的配置信息,譬如"public final static String username = ‘admin’",当然里面的每个属性都是使用"public final static String"关键字去修饰的,所以在这种场景下是无法使用enum去优化代码的。

        那究竟enum的使用场景在哪呢?我们可以从"enum与‘public final static Number’很相似"这个结论来打开突破口。


enum与"public final static Number"的异同?

        前面谈到,enum类型本质上只是由多个具名的数值所组成的集合,这么讲枚举常量确实与‘public final static Number’如出一辙,但关键的不同点在于,我们在枚举类型中定义的枚举常量只能看到变量名,而无法决定变量值是多少,它是由编译器生成的一个不重复、且与其它元素保持顺序的一个long值,从这一点我们不难理解到,枚举常量的价值在于它的名字,而背后支撑它的数值毫无意义。那产生一个问题,为什么枚举要用数值类型而不用其它类型譬如String呢?答案是数值易于确保枚举常量的唯一性与有序性,并且不要忘记,数值是最高效的。

        从这里我们不难分析出两者的异同,"public static final Number"的值是人为设定的一个有意义的值,譬如我们可以用它来定义常量a,b,然后用它们来进行加减乘除运算;而enum的价值仅在于变量名,而背后的数值我们无法控制,换言之枚举常量的意义是:可以用它们来代表某些事物,并且这些事物可以相互区分,仅此而已。


enum的使用场景?

        enum的真正使用场景,也是唯一的使用场景,就是实现状态机。因为它只有这个性质“用它们来代表某些事物,并且这些事物可以相互区分”。

        为了能更加深刻地讲明它的价值,如下会展示出用enum与整型常量实现小型状态机的差异。


"public final static int"的实现情况:

[java]  view plain  copy
  1. public class Fruit {  
  2.     public final static int apple = 1;  
  3.     public final static int orange = 2;  
  4.     public final static int banana = 3;  
  5. }  
  6.   
  7.   
  8. public class Test {  
  9.     public static void f(int fruit) {  
  10.         switch (fruit) {  
  11.             case 1:  
  12.                 System.out.println("This is Apple");  
  13.                 break;  
  14.             case 2:  
  15.                 System.out.println("This is Orange");  
  16.                 break;  
  17.             case 3:  
  18.                 System.out.println("This is Banana");  
  19.                 break;  
  20.             default:  
  21.                 System.out.println("这种食物不是水果!");  
  22.         }  
  23.     }  
  24. }  
        这个状态机用于区分fruit是哪种水果,可以看到明显的缺点在于 数字的自我表达能力不足 ,写出来的代码难以阅读,别人又怎么知道这些整型数值代表的是水果呢?

        于是换一下写法会不会好点:

[java]  view plain  copy
  1. public class Fruit {  
  2.     public final static int apple = 1;  
  3.     public final static int orange = 2;  
  4.     public final static int banana = 3;  
  5. }  
  6.   
  7.   
  8. public class Test {  
  9.     public static void f(int fruit) {  
  10.         switch (fruit) {  
  11.             case Fruit2.apple:  
  12.                 System.out.println("This is Apple");  
  13.                 break;  
  14.             case Fruit2.orange:  
  15.                 System.out.println("This is Orange");  
  16.                 break;  
  17.             case Fruit2.banana:  
  18.                 System.out.println("This is Banana");  
  19.                 break;  
  20.             default:  
  21.                 System.out.println("这种食物不是水果!");  
  22.         }  
  23.     }  
  24. }  
        这种写法与前一种相较来讲,似乎已经 较易于阅读 ,因为我们可以凭变量名来轻易识别出水果的种类,但问题在于它在逻辑上并 不安全 ,因为调用这个方法的人不一定有跟你约法三章,它或许并不知道fruit的范围取值应该是1至3,或者不小心传错了值,这就偏离了我们的原意了,以下你将会看到enum是如何解决这些问题的。


用enum的写法:
[java]  view plain  copy
  1. public enum Fruit {  
  2.     APPLE, ORANGE, BANANA  
  3. }  
  4.   
  5. public static Test {  
  6.     public static void f(Fruit fruit) {  
  7.         switch (fruit) {  
  8.             case APPLE:  
  9.                 break;  
  10.             case ORANGE:  
  11.                 break;  
  12.             case BANANA:  
  13.                 break;  
  14.         }  
  15.     }  
  16. }  

        可以看出,用了enum之后,代码是简洁到极致了,没有一丝的多余,并且最关键是从头到尾就未出现过数值(int),并且是一目了然的。因为它的特性“枚举常量的价值在于常量名,名字让它有自我表达能力,便于我们的区分,而在背后支撑它的数值我们并不关心,编译器决定的值可以做到有序且不重复",完全贴合状态机的需求,因此这段代码并没有出现过我们并不关心的int类型

        同时这里并没有使用"default"分支,因为enum已经保证了安全性,它将值约束在一个范围内了,可以想象当状态机变大时,我们难道要花心思去为一个一个的常量设定数值以保证它们不重复并有序吗?我们只需交给编译器去做就好了。

        最后我们可以知道,enum在比"public final static int"具有更好的表达能力之外,它也因为约束而显得安全,它凭借内部的一个long值作为比特向量,已经可以与非常高效的数值类型作竞争了


结论:

        枚举类型就是为状态机而生的

        没错,enum这种小工具不是必要的,但有时这种优雅而干净地解决问题的手段,对复杂的项目而言是影响深远的,它的这种特性在解决多路分发问题上效果尤其明显,有兴趣的朋友可以去了解。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值