日光之下,並無新事。
——《聖經》
枚舉和註解都是在Java 1.5中引入的,雖然它們是後起之秀,但其功效不可小覷,枚舉改變了常量的聲明方式,註解耦合了數據和代碼。本章就如何更好地使用註解和枚舉提出了多條建議,以便讀者能夠在系統開發中更好地使用它們。
建議83:推薦使用枚舉定義常量
常量聲明是每一個項目都不可或缺的,在Java 1.5之前,我們只有兩種方式的聲明:類常量和接口常量,若在項目中使用的是Java 1.5之前的版本基本上都是如此定義的。不過,在1.5版以後有了改進,即新增了一種常量聲明方式:枚舉聲明常量,看如下代碼:
enum Season{
Spring, Summer, Autumn, Winter;
}
這是一個簡單的枚舉常量命名,清晰又簡單。順便提一句,JLS(Java Language Specification, Java語言規範)提倡枚舉項全部大寫,字母之間用下劃線分隔,這也是從常量的角度考慮的(當然,使用類似類名的命名方式也是比較友好的)。
可能有讀者要問了:枚舉常量與我們經常使用的類常量和靜態常量相比有什麼優勢?問得好,枚舉的優點主要表現在以下四個方面。
(1)枚舉常量更簡單
簡不簡單,我們來對比一下兩者的定義和使用情況就知道了。先把Season枚舉翻寫成接口常量,代碼如下:
interface Season{
int Spring=0;
int Summer=1;
int Autumn=2;
int Winter=3;
}
此處定義了春夏秋冬四個季節,類型都是int,這與Season枚舉的排序值是相同的。首先對比一下兩者的定義,枚舉常量只需要定義每個枚舉項,不需要定義枚舉值,而接口常量(或類常量)則必須定義值,否則編譯通不過,即使我們不需要關注其值是多少也必須定義;其次,雖然兩者被引用的方式相同(都是「類名.屬性」,如Season.Spring),但是枚舉表示的是一個枚舉項,字面含義是春天,而接口常量卻是一個int類型,雖然其字面含義也是春天,但在運算中我們勢必要關注其int值。
(2)枚舉常量屬於穩態型
例如,我們要給外星人描述一下地球上的春夏秋冬是什麼樣子的,使用接口常量應該是這樣寫。
public void describe(int s){
//s變量不能超越邊界,校驗條件
if(s>=0&&s<4){
switch(s){
case Season.Summer:
System.out.println(\"Summer is very hot!\");
break;
case Season.Winter:
System.out.println(\"Winter is very cold!\");
break;
……
}
}
}
很簡單,先使用switch語句判斷是哪一個常量,然後輸出。但問題是我們得對輸入值進行檢查,確定是否越界,如果常量非常龐大,校驗輸入就成了一件非常麻煩的事情,但這是一個不可逃避的過程,特別是如果我們的校驗條件不嚴格,雖然編譯照樣可以通過,但是運行期就會產生無法預知的後果。
我們再來看看枚舉常量是否能夠避免校驗問題,代碼如下:
public void describe(Season s){
switch(s){
case Summer:
System.out.println(Season.Summer+\"is very hot\");
break;
case Winter:
System.out.println(Season.Winter+\"is very cold\");
break;
……
}
}
不用校驗,已經限定了是Season枚舉,所以只能是Season類的四個實例,即春夏秋冬4個枚舉項,想輸入一個int類型或其他類型?門都沒有!這也是我們最看重枚舉的地方:在編譯期間限定類型,不允許發生越界的情況。
(3)枚舉具有內置方法
有一個很簡單的問題:如果要列出所有的季節常量,如何實現呢?接口常量或類常量可以通過反射來實現,這沒錯,只是雖然能實現,但會非常繁瑣,讀者有興趣可以自己寫一個反射類實現此功能(當然,一個一個地手動打印輸出常量,也可以算是列出)。對於此類問題,使用枚舉就可以非常簡單地解決,代碼如下:
public static void main(Stringargs){
for(Season s:Season.values()){
System.out.println(s);
}
}
通過values方法獲得所有的枚舉項,然後打印出來即可。如此簡單,得益於枚舉內置的方法,每個枚舉都是java.lang.Enum的子類,該基類提供了諸如獲得排序值的ordinal方法、compareTo比較方法等,大大簡化了常量的訪問。
(4)枚舉可以自定義方法
這一點似乎並不是枚舉的優點,類常量也可以有自己的方法呀,但關鍵是枚舉常量不僅可以定義靜態方法,還可以定義非靜態方法,而且還能夠從根本上杜絕常量類被實例化。比如我們要在常量定義中獲得最舒服季節的方法,使用常量枚舉的代碼如下所示:
enum Season{
Spring, Summer, Autumn, Winter;
//最舒服的季節
public static Season getComfortableSeason(){
return Spring;
}
}
我們知道每個枚舉項都是該枚舉的一個實例,對於我們的例子來說,也就表示Spring其實是Season的一個實例,Summer也是其中一個實例,那我們在枚舉中定義的靜態方法既可以在類(也就是枚舉Season)中引用,也可以在實例(也就是枚舉項Spring、Summer、Autumn、Winter)中引用,看如下代碼:
public static void main(Stringargs){
System.out.println(\"The most comfortable season is\"+Season.getComfortableSeason());
}
那如果使用類常量要如何實現呢?代碼如下:
class Season{
public final static int Spring=0;
public final static int Summer=1;
public final static int Autumn=2;
public final static int Winter=3;
//最舒服的季節
public static int getComfortableSeason(){
return Spring;
}
}
想想看,我們要怎麼才能打印出\"The most comfortable season is Spring\"這句話呢?除了使用switch判斷外沒有其他辦法了。
雖然枚舉常量在很多方面比接口常量和類常量好用,但是有一點它是比不上接口常量和類常量的,那就是繼承,枚舉類型是不能有繼承的,也就是說一個枚舉常量定義完畢後,除非修改重構,否則無法做擴展,而接口常量和類常量則可以通過繼承進行擴展。但是,一般常量在項目構建時就定義完畢了,很少會出現必須通過擴展才能實現業務邏輯的場景。
注意 在項目開發中,推薦使用枚舉常量代替接口常量或類常量。