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