首页 > 代码库 > JAVA中的枚举(二)

JAVA中的枚举(二)

到目前为止,我们仅仅使用了最简单的语法定义枚举类型,其实枚举类型可以做更多的事情,在Tiger的定义中,枚举是一种新的类型,允许用常量来表示特定的数据片断,它能胜任普通类的大部分功能,如定义自己的构造函数、方法、属性等等。这也是Java与C/C++或是Pascal中不同的地方,在那两种语言中枚举类型代表的就是一些int类型的数字,但在Java中枚举更像是一个类。

    接下来我们将丰富一下我们的枚举类型。

 前面定义了包含五个工作日的枚举类型,但是真正在每个工作日进行操作的动作是在其它类中的printWeekDay方法中进行的。假设我们经过分析发现对工作日的操作应该属于枚举类型WeekDay的职责,那我们就可以把枚举类型改造如下:

public enum WeekDay {
                   MONDAY, TUESDAY, WENSDAY, THURSDAY, FRIDAY;
         
                   /**
                * 根据工作日的不同打印不同的信息。
                */
                 public void printWeekDay(){
                  switch(this){
                   case MONDAY: 
System.out.println(“Today is Monday!”);
break;
                   case TUESDAY: 
System.out.println(“Today is Tuesday!”);
break;
                   case WENSDAY: 
System.out.println(“Today is Wensday!”);
break;
                   case THURSDAY: 
System.out.println(“Today is Thursday!”);
break;     
   case FRIDAY: 
System.out.println(“Today is Friday!”);
break;     
                   default:
                     throw new AssertionError("Unexpected value: " + this);
                    }
               }
}
 
//测试程序
for(WeekDay weekDay: EnumSet.allOf(WeekDay.class)){
          System.out.println("the message is : "+weekDay.printWeekDay());
         }

现在的枚举类型Operation变得丰满多了,我们在枚举类型WeekDay中增加了一个printWeekDay方法,你也可以用WeekDay.MONDAY.printWeekDay()方法来进行信息的输出了。

    枚举类型也允许定义自己的构造函数,这使得枚举常量可以初始化更多的信息。来看看我们在EnumMap与EnumSet一文中提到过的枚举类型DataBaseType,它存放了现在支持的所有数据库类型。但它仅是一个“代号”,由于和数据库相关的信息对于一个应用程序来说是固定不变的,所以把这些数据放置在枚举类型自身中更符合设计的习惯。

public enum DataBaseType{
         
                   MySQL("com.mysql.jdbc.Driver", "jdbc:mysql://localhost/mydb"),
                   Oracle("oracle.jdbc.driver.OracleDriver",
                                     "jdbc:oracle:thin:@localhost:1521:sample"), 
                   DB2("com.ibm.db2.jdbc.app.DB2Driver",
                                     "jdbc:db2://localhost:5000/sample"), 
                   SQLSERVER("com.microsoft.jdbc.sqlserver.SQLServerDriver",
                                     "jdbc:microsoft:sqlserver://localhost:1433;DatabaseName=mydb");
         
                   private String driver;
         
                   private String url;
                   //自定义的构造函数,它为驱动、URL赋值
                   DataBaseType(String driver,String url){
                            this.driver = driver;
                            this.url = url;
                   }
 
                   /**
                * 获得数据库驱动
                * @return
              */
                public String getDriver() {
                            return driver;
                }
 
                   /**
                * 获得数据库连接URL
                * @return
                */
                   public String getUrl() {
                            return url;
                   }
}
 
//测试程序
for(DataBaseType dataBaseType: EnumSet.allOf(DataBaseType.class)){
              System.out.println("the driver is : "+dataBaseType.getDriver());
              System.out.println("the url is : "+dataBaseType.getUrl());
         }

你注意到例子中的枚举常量是如何声明使用自定义构造函数初始化的吗?仅需要将初始化使用的数据放入在枚举常量名称后面的括号中就可以了。

    现在我们设计出了两个内容丰富的枚举类型,对枚举类型的使用也变得自然了许多。你也许觉得枚举类型和类之间差别甚微。可是毕竟枚举类型有着诸多限制,你在实现自己的枚举类型时一定要遵循它们。

注意:

    1. 枚举类型不能使用extends关键字,但是可以使用implements关键字。这样我们可以把不同枚举类型共有的行为提取到接口中,来规范枚举类型的行为。

    2. 枚举类型的自定义构造函数并不能覆盖默认执行的构造函数,它会跟在默认构造函数之后执行。

    3. 枚举类型的自定义构造函数必须是私有的。你不需要在构造函数上添加private关键字,编译器会为我们代劳的。

    4. 枚举类型中枚举常量的定义必须放在最上面,其后才能是变量和方法的定义。

模板方法
谈这个话题前我们要看一下改写的printWeekDay方法,在那个例子里WeekDay是丰富一些了,不过使用switch对枚举常量逐个判断以便定制不同的行为,扩展起来要麻烦了一些。假如为WeekDay添加了一个新的枚举常量,如果你忘了同时为它在switch中添加相应的case标示,那么即使有default标示来提示错误,也只能在运行后才能发现。

    怎么做能更好一点?我们前面已经认识到枚举就是一个特殊的类,它可以有方法和属性,同时每个声明的枚举项都是这个枚举类型的一个实例。那么我们能不能使用“模板方法模式”来改造一下这个枚举类呢?当然可以!我们把那个例子重构一下,变成下面这个样子:

public enum WeekDay {
                   MONDAY{
                            @Override
                            public void printWeekDay() {
                                     System.out.println(“Today is Monday!”);
                            }
                   },
                   TUESDAY{
                            @Override
                            public void printWeekDay() {
                                     System.out.println(“Today is Tuesday!”);
                            }
                   },
                   WENSDAY{
                            @Override
                            public void printWeekDay() {
                                     System.out.println(“Today is Wensday!”);
                            }
                   },
                   THURSDAY{
                            @Override
                            public void printWeekDay() {
                                   System.out.println(“Today is Thursday!”);
                            }
                   },
                   FRIDAY{
                            @Override
                            public void printWeekDay() {
                                     System.out.println(“Today is Friday!”);
                            }
                   };
         
                   /**
                * 根据工作日的不同打印不同的信息
                */
                   public abstract void printWeekDay();
 
}

首先,我们把方法printWeekDay改为抽象方法,然后我们在每一个枚举常量中实现了在枚举类型里定义的这个抽象方法。这样,每为枚举类型添加一个新的枚举常量,都必须实现枚举类型中定义的抽象方法,不然编译器提示出错。之所以可以这么做的原因是,虚拟机将枚举类型中声明的每一个枚举常量,创建成为一个单独的枚举类型的子类。

    这样,再配合使用Tiger里的静态导入,调用者的代码就可以这样写了:

MONDAY.printWeekDay();
TUESDAY.printWeekDay();
//or better...
getWeekDay().printWeekDay();

这些代码显然要比常见的if(weekDay == WeekDay.MONDAY){...} else if(weekDay == WeekDay.TUESDAY) else {...}形式强多了,它们易读、容易扩展和维护。

反向查找
前面说到枚举也可以自定义构造函数,可以用属性来关联更多的数据。那如果我们有这样的一种需要该怎么办呢?——我们需要根据关联的数据来得到相应的枚举项,例如下面的这种情况:

public final enum Status {
     WAITING(0),
     READY(1),
     SKIPPED(-1),
     COMPLETED(5);
 
     private int code;
 
     private Status(int code) {
          this.code = code;
     }
 
     public int getCode() { return code; }
}

这里每种Status对应了一个code,WAITING对应了0,而COMPLETED对应了5。如果想通过0得到WAITING这个枚举项要怎么做?

    做法也很简单,使用一个静态的java.util.Map来把code和枚举项关联起来就可以了,就像这样:

public final enum Status {
     WAITING(0),
     READY(1),
     SKIPPED(-1),
     COMPLETED(5);
 
     private static final Map<Integer,Status> lookup 
          = new HashMap<Integer,Status>();
 
     static {
          for(Status s : EnumSet.allOf(Status.class)){
               lookup.put(s.getCode(), s);
          }
     }
 
     public static Status get(int code) { 
          return lookup.get(code); 
     }
 
     private int code;
 
     private Status(int code) {
          this.code = code;
     }
 
     public int getCode() { return code; }
}

静态方法get(int)提供了需求中的反向查找能力,而静态块里使用EnumSet来把起映射做用的Map组装起来,Over!

总结:使用枚举,但不要滥用!

    学习任何新版语言的一个危险就是疯狂使用新的语法结构。如果这样做,那么您的代码就会突然之间有 80% 是泛型、标注和枚举。所以,应当只在适合使用枚举的地方才使用它。那么,枚举在什么地方适用呢?一条普遍规则是,任何使用常量的地方,例如目前用 switch 代码切换常量的地方。如果只有单独一个值(例如,鞋的最大尺寸,或者笼子中能装猴子的最大数目),则还是把这个任务留给常量吧。但是,如果定义了一组值,而这些值中的任何一个都可以用于特定的数据类型,那么将枚举用在这个地方最适合不过。


转载至:http://blog.csdn.net/shimiso/article/details/5909239

本文出自 “ciyo技术分享” 博客,请务必保留此出处http://ciyorecord.blog.51cto.com/6010867/1934197

JAVA中的枚举(二)