1. 程式人生 > >JAVA中的枚舉(二)

JAVA中的枚舉(二)

enum 枚舉

到目前為止,我們僅僅使用了最簡單的語法定義枚舉類型,其實枚舉類型可以做更多的事情,在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中的枚舉(二)