讀古今文學網 > 編寫高質量代碼:改善Java程序的151個建議 > 第6章 枚舉和註解 >

第6章 枚舉和註解

日光之下,並無新事。

——《聖經》

枚舉和註解都是在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判斷外沒有其他辦法了。

雖然枚舉常量在很多方面比接口常量和類常量好用,但是有一點它是比不上接口常量和類常量的,那就是繼承,枚舉類型是不能有繼承的,也就是說一個枚舉常量定義完畢後,除非修改重構,否則無法做擴展,而接口常量和類常量則可以通過繼承進行擴展。但是,一般常量在項目構建時就定義完畢了,很少會出現必須通過擴展才能實現業務邏輯的場景。

注意 在項目開發中,推薦使用枚舉常量代替接口常量或類常量。