代码改变世界

Java中有些好的特性(二):枚举

2010-10-26 23:20  横刀天笑  阅读(5398)  评论(17编辑  收藏  举报

前言

我在写上一篇时,开始选了一个很土的名字“Java超过C#的地方”,然后引起了某些同学的不满,后来修改了个名。我在这里再次申明一下,我没有贬低任何语言之意,纯粹是从一个.NET程序员的角度去看Java,看看是不是有些什么在开发中觉得比较“爽”的东西。务必请用专业的态度来比较各种技术的长短。

Java的枚举是一个“class”

Java的早期版本时没有enum的,大家都用interface里加常量的方式实现,Java 1.5版本之后添加了对enum的支持。

Java认为类C的枚举不是类型安全的(type safe),所以Java中没有枚举,Java的枚举就是类(Enum Class),只是用了一个enum代替class而已(虽然.NET里在MSIL层面枚举最终也是一种“class”)。因为Java的枚举是类,所以就带来一些与C#中的枚举有趣的不同点,比如可以添加方法,可以添加属性。请看下面的Java代码:

   1: public enum Role{
   2:     Admin("管理员"),
   3:     Owner("所有者"),
   4:     Creator("创建者");
   5:  
   6:     private String displayName;
   7:  
   8:     Role(String displayName){
   9:         this.displayName = displayName;
  10:     }
  11:  
  12:     public String getDisplayName(){
  13:         return this.displayName;
  14:     }
  15: }

Java的枚举居然可以这么写!枚举里可以有方法。这里的Admin,Owner,Creator实际上是Role类的常量,调用Role(String)构造器,但是Java不允许你将enum的构造器设为public,由Java自己内部调用。

有人可能会问这样有什么用处呢?比如上面这段代码,建一个枚举表示系统中的所有角色,为了用户友好性,数据库里我们保存角色名字,但是在系统界面上我们要显示一个更具描述性的名字,而且假如我们的系统需要多语言化,那么在不同的语言平台上的界面上我们的系统应该显示不同的描述性短语(注意:.NET中的枚举是不能国际化的),比如下面的界面:

image

image

那我们就可以这样做:

   1: public enum Role{
   2:     Admin("role-admin"),
   3:     Owner("role-owner"),
   4:     Creator("role-creator");
   5:  
   6:     private String displayName;
   7:  
   8:     Role(String displayName){
   9:         this.displayName = displayName;
  10:     }
  11:  
  12:     public String getDisplayName(){
  13:         return Resource.get(this.displayName);
  14:     }
  15: }

我们这里getDisplayName的时候调用一个国际化的API,通过枚举构造器里传入的参数作为key获取国际化的表示。

Java的enum是类型安全、版本安全的

这样一说好像C#的enum不是类型安全的一样,哼!嗯,某种角度上来说C#(本文以C#指代整个.NET平台)的枚举真的不是类型安全的。比如下面的代码:

   1: using System;
   2: public enum Role:short
   3: {
   4:     Admin,Owner,Creator
   5: }
   6:  
   7: public class Program
   8: {
   9:     public static void Main(String[] args)
  10:     {
  11:         byte i = (byte)Role.Owner;
  12:     }
  13: }

即使我们给C#的enum指定了underlying type,但是我们还是可以将其任意的与基本类型进行转换。而且这种转换是静悄悄地进行的,编译器和运行时都不会提醒我们。

C#的enum完全是一个常量,所以跟C#的const一样还会带来版本的问题。比如A程序集定义了一个枚举Role,B程序集引用A程序集,使用Role,实际上在运行时B程序集不再需要A程序集了(大家知道什么原因吧)。这样如果A程序集修改了Role,比如修改了枚举常量的顺序:从Admin,Owner,Creator变为Admin,Creator,Owner。而B程序集没有重新编译,这个时候就会出现问题。

C#中实现Enum class

实际上Enum Class是一种模式,只是java的enum将这种模式放到了语言中,我们在C#里一样可以实现:

   1: public sealed Class Role
   2: {
   3:     public static readonly Role Admin = new Role("role-admin");
   4:     public static readonly Role Owner = new Role("role-owner");
   5:     public static readonly Role Creator = new Creator("role-creator");
   6:  
   7:     private String displayName;
   8:  
   9:     private Role(String displayName)
  10:     {
  11:         this.displayName = displayName;
  12:     }
  13:     
  14:     public String DisplayName
  15:     {
  16:         get
  17:         {
  18:             Resource.Get(this.displayName);
  19:         }
  20:     }
  21: }

只是实现起来比java的默认实现繁琐一点而已,毕竟那是人家语言的first class。

位标记

如果在C#的enum上添加[Flags]的特性话,这个enum就不再是enum了,就变成了位标记。位标记这东西在实现某些功能时确实非常滴方便,比如一个文件的属性:只读,只写,可读写,比如权限控制系统等等。Java的枚举就不能添加这么一个annotation了,不过Java有个EnumSet,我们借助这个API实现位标记同样的功能。

后记

Java的enum与C#的enum各有特色,Java的enum更灵活一些,C#的enum更轻型一些,但是使用时请注意C#的enum的一些问题。