咱們先來寫一個簡單的枚舉類型的定義:java
Java代碼mysql
[java] view plaincopyweb
<EMBED id=ZeroClipboardMovie_1 height=18 name=ZeroClipboardMovie_1 type=application/x-shockwave-flash align=middle pluginspage=http://www.macromedia.com/go/getflashplayer width=18 src=http://static.blog.csdn.net/scripts/ZeroClipboard/ZeroClipboard.swf wmode="transparent" flashvars="id=1&width=18&height=18" allowfullscreen="false" allowscriptaccess="always" bgcolor="#ffffff" quality="best" menu="false" loop="false">sql
public enum WeekDay{ 數據庫
MONDAY, TUESDAY, WENSDAY, THURSDAY, FRIDAY; //最後這個「;」可寫可不寫。 數組
} oracle
這和類、接口的定義很相像嘛!Tiger(jdk1.5的代號)中的枚舉類型就是一種使用特殊語法「enum」定義的類。全部的枚舉類型是java.lang.Enum的子類。這是Tiger中新引入的一個類,它自己並非枚舉類型,但它定義了全部枚舉類型所共有的行爲。 app
注意:雖然全部的枚舉類型都繼承自java.lang.Enum,可是你不能繞過關鍵字「enum」而使用直接繼承Enum的方式來定義枚舉類型。編譯器會提示錯誤來阻止你這麼作。ide
WeekDay中定義的五個枚舉常量之間使用「,」分割開來。這些常量默認都是「public static final」的,因此你就沒必要再爲它們加上「public static final」修飾(編譯器會提示出錯),這也是爲何枚舉常量採用大寫字母來命名的緣由。並且每個常量都是枚舉類型WeekDay的一個實例。你能夠經過相似「WeekDay.MONDAY」這種格式來獲取到WeekDay中定義的枚舉常量,也能夠採用相似「WeekDay oneDay = WeekDay.MONDAY」的方式爲枚舉類型變量賦值(你不能給枚舉類型變量分配除了枚舉常量和null之外的值,編譯器會提示出錯)。函數
做爲枚舉類型實例的枚舉常量是如何初始化的呢?其實答案很簡單,這些枚舉常量都是經過Enum中定義的構造函數進行初始化的。
Java代碼
[java] view plaincopy
<EMBED id=ZeroClipboardMovie_2 height=18 name=ZeroClipboardMovie_2 type=application/x-shockwave-flash align=middle pluginspage=http://www.macromedia.com/go/getflashplayer width=18 src=http://static.blog.csdn.net/scripts/ZeroClipboard/ZeroClipboard.swf wmode="transparent" flashvars="id=2&width=18&height=18" allowfullscreen="false" allowscriptaccess="always" bgcolor="#ffffff" quality="best" menu="false" loop="false">
//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代碼
[java] view plaincopy
<EMBED id=ZeroClipboardMovie_3 height=18 name=ZeroClipboardMovie_3 type=application/x-shockwave-flash align=middle pluginspage=http://www.macromedia.com/go/getflashplayer width=18 src=http://static.blog.csdn.net/scripts/ZeroClipboard/ZeroClipboard.swf wmode="transparent" flashvars="id=3&width=18&height=18" allowfullscreen="false" allowscriptaccess="always" bgcolor="#ffffff" quality="best" menu="false" loop="false">
/**
* 根據日期的不一樣輸出相應的日期信息。
* @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代碼
[java] view plaincopy
<EMBED id=ZeroClipboardMovie_4 height=18 name=ZeroClipboardMovie_4 type=application/x-shockwave-flash align=middle pluginspage=http://www.macromedia.com/go/getflashplayer width=18 src=http://static.blog.csdn.net/scripts/ZeroClipboard/ZeroClipboard.swf wmode="transparent" flashvars="id=4&width=18&height=18" allowfullscreen="false" allowscriptaccess="always" bgcolor="#ffffff" quality="best" menu="false" loop="false">
//現支持的數據庫類型枚舉類型定義
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提供了許多工廠方法以便於初始化,EnumSet做爲Set接口實現,它支持對包含的枚舉常量的遍歷:
Java代碼
[java] view plaincopy
<EMBED id=ZeroClipboardMovie_5 height=18 name=ZeroClipboardMovie_5 type=application/x-shockwave-flash align=middle pluginspage=http://www.macromedia.com/go/getflashplayer width=18 src=http://static.blog.csdn.net/scripts/ZeroClipboard/ZeroClipboard.swf wmode="transparent" flashvars="id=5&width=18&height=18" allowfullscreen="false" allowscriptaccess="always" bgcolor="#ffffff" quality="best" menu="false" loop="false">
for(Operation op : EnumSet.range(Operation.PLUS , Operation.MULTIPLY)) {
doSomeThing(op);
}
到目前爲止,咱們僅僅使用了最簡單的語法定義枚舉類型,其實枚舉類型能夠作更多的事情,在Tiger的定義中,枚舉是一種新的類型,容許用常量來表示特定的數據片段,它能勝任普通類的大部分功能,如定義本身的構造函數、方法、屬性等等。這也是Java與C/C++或是Pascal中不一樣的地方,在那兩種語言中枚舉類型表明的就是一些int類型的數字,但在Java中枚舉更像是一個類。
接下來咱們將豐富一下咱們的枚舉類型。
前面定義了包含五個工做日的枚舉類型,可是真正在每一個工做日進行操做的動做是在其它類中的printWeekDay方法中進行的。假設咱們通過分析發現對工做日的操做應該屬於枚舉類型WeekDay的職責,那咱們就能夠把枚舉類型改造以下:
Java代碼
[java] view plaincopy
<EMBED id=ZeroClipboardMovie_6 height=18 name=ZeroClipboardMovie_6 type=application/x-shockwave-flash align=middle pluginspage=http://www.macromedia.com/go/getflashplayer width=18 src=http://static.blog.csdn.net/scripts/ZeroClipboard/ZeroClipboard.swf wmode="transparent" flashvars="id=6&width=18&height=18" allowfullscreen="false" allowscriptaccess="always" bgcolor="#ffffff" quality="best" menu="false" loop="false">
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,它存放了如今支持的全部數據庫類型。但它僅是一個「代號」,因爲和數據庫相關的信息對於一個應用程序來講是固定不變的,因此把這些數據放置在枚舉類型自身中更符合設計的習慣。
Java代碼
[java] view plaincopy
<EMBED id=ZeroClipboardMovie_7 height=18 name=ZeroClipboardMovie_7 type=application/x-shockwave-flash align=middle pluginspage=http://www.macromedia.com/go/getflashplayer width=18 src=http://static.blog.csdn.net/scripts/ZeroClipboard/ZeroClipboard.swf wmode="transparent" flashvars="id=7&width=18&height=18" allowfullscreen="false" allowscriptaccess="always" bgcolor="#ffffff" quality="best" menu="false" loop="false">
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標示來提示錯誤,也只能在運行後才能發現。
怎麼作能更好一點?咱們前面已經認識到枚舉就是一個特殊的類,它能夠有方法和屬性,同時每一個聲明的枚舉項都是這個枚舉類型的一個實例。那麼咱們能不能使用「模板方法模式」來改造一下這個枚舉類呢?固然能夠!咱們把那個例子重構一下,變成下面這個樣子:
Java代碼
[java] view plaincopy
<EMBED id=ZeroClipboardMovie_8 height=18 name=ZeroClipboardMovie_8 type=application/x-shockwave-flash align=middle pluginspage=http://www.macromedia.com/go/getflashplayer width=18 src=http://static.blog.csdn.net/scripts/ZeroClipboard/ZeroClipboard.swf wmode="transparent" flashvars="id=8&width=18&height=18" allowfullscreen="false" allowscriptaccess="always" bgcolor="#ffffff" quality="best" menu="false" loop="false">
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裏的靜態導入,調用者的代碼就能夠這樣寫了:
Java代碼
[java] view plaincopy
<EMBED id=ZeroClipboardMovie_9 height=18 name=ZeroClipboardMovie_9 type=application/x-shockwave-flash align=middle pluginspage=http://www.macromedia.com/go/getflashplayer width=18 src=http://static.blog.csdn.net/scripts/ZeroClipboard/ZeroClipboard.swf wmode="transparent" flashvars="id=9&width=18&height=18" allowfullscreen="false" allowscriptaccess="always" bgcolor="#ffffff" quality="best" menu="false" loop="false">
MONDAY.printWeekDay();
TUESDAY.printWeekDay();
//or better...
getWeekDay().printWeekDay();
這些代碼顯然要比常見的if(weekDay == WeekDay.MONDAY){...} else if(weekDay == WeekDay.TUESDAY) else {...}形式強多了,它們易讀、容易擴展和維護。
反向查找
前面說到枚舉也能夠自定義構造函數,能夠用屬性來關聯更多的數據。那若是咱們有這樣的一種須要該怎麼辦呢?——咱們須要根據關聯的數據來獲得相應的枚舉項,例以下面的這種狀況:
Java代碼
[java] view plaincopy
<EMBED id=ZeroClipboardMovie_10 height=18 name=ZeroClipboardMovie_10 type=application/x-shockwave-flash align=middle pluginspage=http://www.macromedia.com/go/getflashplayer width=18 src=http://static.blog.csdn.net/scripts/ZeroClipboard/ZeroClipboard.swf wmode="transparent" flashvars="id=10&width=18&height=18" allowfullscreen="false" allowscriptaccess="always" bgcolor="#ffffff" quality="best" menu="false" loop="false">
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和枚舉項關聯起來就能夠了,就像這樣:
Java代碼
[java] view plaincopy
<EMBED id=ZeroClipboardMovie_11 height=18 name=ZeroClipboardMovie_11 type=application/x-shockwave-flash align=middle pluginspage=http://www.macromedia.com/go/getflashplayer width=18 src=http://static.blog.csdn.net/scripts/ZeroClipboard/ZeroClipboard.swf wmode="transparent" flashvars="id=11&width=18&height=18" allowfullscreen="false" allowscriptaccess="always" bgcolor="#ffffff" quality="best" menu="false" loop="false">
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 代碼切換常量的地方。若是隻有單獨一個值(例如,鞋的最大尺寸,或者籠子中能裝猴子的最大數目),則仍是把這個任務留給常量吧。可是,若是定義了一組值,而這些值中的任何一個均可以用於特定的數據類型,那麼將枚舉用在這個地方最適合不過。