在實際編程中,往往存在著這樣的“資料集”,它們的數值在程式中是穩定的,而且“資料集”中的元素是有限的。例如星期一到星期日七個資料元素組成了一周的“資料集”,花牌 (春夏秋冬)四個資料元素組成了四季的“資料集”。在Java中想表示這種資料集最容易想到的寫法可能是這樣,我們以表示一周五天的工作日來舉例:
Java代碼
public class WeekDay {
public static final int MONDAY = 1;
public static final int TUESDAY = 2;
public static final int WENSDAY = 3;
public static final int THURSDAY = 4;
public static final int FRIDAY = 5;
}
現在,你的類就可以使用像WeekDay.TUESDAY這樣的常量了。但是這裡隱藏著一些問題,這些常量是Java中int類型的常量,這意味著該方法可以接受任何int 類型的值,即使它和WeekDay中定義的所有日期都對應不上。因此,您需要檢測上界和下界,在出現無效值的時候,可能還要拋出一個IllegalArgumentException。而且,如果後來又添加另外一個日期(例如WeekDay.SATURDAY ),那麼必須改變所有代碼中的上界,才能接受這個新值。 換句話說,在使用這類帶有整型常量的類時,這個方案也許可行,但並不是非常有效。
Joshua Bloch老大這時站了出來,在他的著作《Effective Java》中提出了在這樣情境下的一種非常好的模式——Type-safe enumeration pattern。這個模式簡而言之就是給這樣的常量類一個私人的構造方法,然後聲明一些public static final 的同類型的變數暴露給使用者,例如:
Java代碼
public class WeekDay {
public static final WeekDay MONDAY = new WeekDay(1);
public static final WeekDay TUESDAY = new WeekDay(2);
public static final WeekDay WENSDAY = new WeekDay(3);
public static final WeekDay THURSDAY = new WeekDay(4);
public static final WeekDay FRIDAY = new WeekDay(5);
public int getValue(){
return value;
}
private int value;
private WeekDay(int i){
this.value = i;
}
//other methods...
}
這樣做的好處是你的程式現在接受的不是int類型的資料了,而是WeekDay的一個預定義好的static final的執行個體(WeekDay.TUESDAY等 ),例如:
Java代碼
public void setWeekDay(WeekDay weekDay){...}
而這樣做也避免了可以隨意向方法中傳遞一個不合法的int型數值(例如-1)而造成程式錯誤。同時,它還會帶來其他的一些好處:由於這些枚舉的對象都是一些類的執行個體,所以在裡面放一些需要的屬性來存放資料;又由於他們都是單例的,你可以使用equals方法或是==符號來比較它們。
Joshua Bloch大大提出的枚舉模式,很好用但是好麻煩啊。如果你用過C/C++或是Pascal這樣的語言的話一定會對它們的枚舉類型有印象,例如在C/C++中我們可以這樣定義:
C/C++代碼
enum weekday {
MONDAY,
TUESDAY,
WENSDAY,
THURSDAY,
FRIDAY
};
然後在程式中就可以用MONDAY、TUESDAY這些變數了。這樣多方便,但是Java 1.4以前的版本並沒有提供枚舉類型的支援,所以如果你是用JDK 1.4開發程式的話就只能像Joshua Bloch老大那樣寫了。從Java 5.0(代號為Tiger)開始,這種情況改變了,Java從語言層面支援了枚舉類型。
枚舉是Tiger的一個很重要的新特性,它是一種新的類型,允許用常量來表示特定的資料片斷,而且全部都以型別安全的形式來表示,它使用“enum”關鍵字來定義。
我們先來寫一個簡單的枚舉類型的定義:
Java代碼
public enum WeekDay{
MONDAY, TUESDAY, WENSDAY, THURSDAY, FRIDAY; //最後這個“;”可寫可不寫。
}
這和類、介面的定義很相像嘛!Tiger中的枚舉類型就是一種使用特殊文法“enum”定義的類。所有的枚舉類型是java.lang.Enum的子類。這是Tiger中新引入的一個類,它本身並不是枚舉類型,但它定義了所有枚舉類型所共有的行為,如下表:
注意:雖然所有的枚舉類型都繼承自java.lang.Enum,但是你不能繞過關鍵字“enum”而使用直接繼承Enum的方式來定義枚舉類型。編譯器會提示錯誤來阻止你這麼做。
WeekDay中定義的五個枚舉常量之間使用“,”分割開來。這些常量預設都是“public static final”的,所以你就不必再為它們加上“public static final”修飾(編譯器會提示出錯),這也是為什麼枚舉常量採用大寫字母來命名的原因。而且每一個常量都是枚舉類型WeekDay的一個執行個體。你可以通過類似“WeekDay.MONDAY”這種格式來擷取到WeekDay中定義的枚舉常量,也可以採用類似“WeekDay oneDay = WeekDay.MONDAY”的方式為枚舉類型變數賦值(你不能給枚舉類型變數分配除了枚舉常量和null以外的值,編譯器會提示出錯)。
作為枚舉類型執行個體的枚舉常量是如何初始化的呢?其實答案很簡單,這些枚舉常量都是通過Enum中定義的建構函式進行初始化的。
Java代碼
//java.lang.Enum中定義的建構函式,
//兩個參數分別是定義的枚舉常量名稱以及它所在的次序。
protected Enum(String name, int ordinal) {
this.name = name;
this.ordinal = ordinal;
}
在初始化的過程中,枚舉常量的次序是按照聲明的順序安排的。第一個枚舉常量的次序是0,依此累加。
枚舉類型除了擁有Enum提供的方法以外,還存在著兩個隱藏著的與具體枚舉類型相關的靜態方法——values()和valueOf(String arg0)。方法values()可以獲得包含所有枚舉常量的數組;方法valueOf是java.lang.Enum中方法valueOf的簡化版本,你可以通過它,根據傳遞的名稱來得到當前枚舉類型中匹配的枚舉常量。
我們來看一個枚舉類型使用的小例子。需求中要求可以對指定的日期進行相應的資訊輸出。對於這麼簡單的需求,這裡就使用枚舉類型來進行處理。前面我們已經定義好了包含有五個工作日的枚舉類型。下面的代碼則是進行輸出的方法:
Java代碼
/**
* 根據日期的不同輸出相應的日期資訊。
* @param weekDay 代表不同日期的枚舉常量
*/
public void printWeekDay(WeekDay weekDay){
switch(weekDay){
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 hursday!”);
break;
case FRIDAY:
System.out.println(“Today is Friday!”);
break;
default:
throw new AssertionError("Unexpected value: " + weekDay);
}
}
在Tiger以前,switch操作僅能對int、short、char和byte進行操作。而在Tiger中,switch增加了對枚舉類型的支援,因為枚舉類型僅含有有限個可以使用整數代替的枚舉常量,這太適合使用switch語句了!就像上面代碼中那樣,你在swtich運算式中放置枚舉類型變數,就可以在case標示中直接使用枚舉類型中的枚舉常量了。
注意:case標示的寫法中沒有枚舉類型首碼,這意味著不能將代碼寫成 case Operation. PLUS,只需將其寫成 case PLUS即可。否則,編譯器會提示出錯資訊。
像上面的例子一樣,雖然你已經在case標示中窮盡了某個枚舉類型中的所有枚舉常量,但還是建議你在最後加上default標示(就像上面代碼示意的那樣)。因為萬一為枚舉類型添加一個新的枚舉常量,而忘了在switch中添加相應的處理,是很難發現錯誤的。
為了更好的支援枚舉類型,java.util中添加了兩個新類:EnumMap和EnumSet。使用它們可以更高效的操作枚舉類型。下面我一一介紹給你:
EnumMap是專門為枚舉類型量身定做的Map實現。雖然使用其它的Map實現(如HashMap)也能完成枚舉類型執行個體到值得映射,但是使用EnumMap會更加高效:它只能接收同一枚舉類型的執行個體作為索引值,並且由於枚舉類型執行個體的數量相對固定並且有限,所以EnumMap使用數組來存放與枚舉類型對應的值。這使得EnumMap的效率非常高。
提示:EnumMap在內部使用枚舉類型的ordinal()得到當前執行個體的聲明次序,並使用這個次序維護枚舉類型執行個體對應值在數組的位置。
下面是使用EnumMap的一個程式碼範例。枚舉類型DataBaseType裡存放了現在支援的所有資料庫類型。針對不同的資料庫,一些資料庫相關的方法需要返回不一樣的值,樣本中getURL就是一個。
Java代碼
//現支援的資料庫類型枚舉類型定義
public enum DataBaseType{
MYSQL,ORACLE,DB2,SQLSERVER
}
//某類中定義的擷取資料庫URL的方法以及EnumMap的聲明。
……
private EnumMap<DataBaseType ,String> urls =
new EnumMap<DataBaseType ,String>(DataBaseType.class);
public DataBaseInfo(){
urls.put(DataBaseType.DB2,"jdbc:db2://localhost:5000/sample");
urls.put(DataBaseType.MYSQL,"jdbc:mysql://localhost/mydb");
urls.put(DataBaseType.ORACLE,"jdbc:oracle:thin:@localhost:1521:sample");
urls.put(DataBaseType.SQLSERVER,"jdbc:microsoft:sqlserver://localhost:1433;DatabaseName=mydb");
}
/**
* 根據不同的資料庫類型,返回對應的URL
* @param type DataBaseType枚舉類新執行個體
* @return
*/
public String getURL(DataBaseType type){
return this.urls.get(type);
}
在實際使用中,EnumMap對象urls往往是由外部負責整個應用初始化的代碼來填充的。這裡為了示範方便,類自己做了內容填充。
像例子中那樣,使用EnumMap可以很方便的為枚舉類型在不同的環境中綁定到不同的值上。如:例子中getURL綁定到URL上,在其它的代碼中可能又被綁定到資料庫驅動上去。
EnumSet是枚舉類型的高效能Set實現。它要求放入它的枚舉常量必須屬於同一枚舉類型。EnumSet提供了許多Factory 方法以便於初始化,見下表:
EnumSet作為Set介面實現,它支援對包含的枚舉常量的遍曆:
Java代碼
for(Operation op : EnumSet.range(Operation.PLUS , Operation.MULTIPLY)) {
doSomeThing(op);
}