本文將介紹J2SE 5.0中三個比較重要的特性: 枚舉類型, 注釋類型, 範型, 並在此基礎上介紹在如何在Eclipse 3.1開發環境中開發枚舉類型, 注釋類型和範型應用。
J2SE 5.0 (Tiger)的發布是Java語言發展史上的一個重要的裡程碑, 是迄今為止在 Java 編程方面所取得的最大進步.
J2SE 5.0提供了很多令人激動的特性.這些特性包括範型(generics)的支援, 枚舉類型(enumeration)的支援, 中繼資料(metadata)的支援, 自動拆箱(unboxing)/裝箱(autoboxing), 可變個數參數(varargs), 靜態匯入(static imports), 以及新的線程架構(Thread framework).
隨著J2SE 5.0的推出, 越來越多的整合式開發環境(IDE)支援J2SE 5.0的開發. 著名的開源Java IDE Eclipse從3.1M4開始支援J2SE 5.0的開發, 目前最新的版本是3.1RC4.
本系列將介紹J2SE 5.0中三個比較重要的特性: 枚舉類型, 注釋類型, 範型, 並在此基礎上介紹在如何在Eclipse 3.1開發環境中開發枚舉類型, 注釋類型和範型應用.本文將介紹枚舉類型.
1. 枚舉類型
1.1枚舉類型簡介
J2SE 5.0 以及之前的JDK有兩種基本方法可以來定義新類型:通過Classes 以及Interface. 對於大部分物件導向編程來說,這兩種方法看起來似乎足夠了.但是在一些特殊情況下,這些方法就不適合.例如,我們想定義一個類型 Priority, 它只能接受 High, Medium, Low 三種值. 其他任何值都是非法的.J2SE 5.0 以前的JDK是可以構造這種類型的,但是需要做很多工作,有可能會帶來如不安全(型別安全問題???)等潛在問題,而J2SE 5.0的枚舉類型(Enum) 能避免這些問題.
Eclipse 是JAVA程式員最常用的開發平台,而Eclipse 3.1提供對J2SE 5.0的支援,它為J2SE 5.0的新功能提供了協助工具.在對枚舉類型的支援上,它不僅提供了枚舉類型的建立模板,而且為枚舉類型的各種開發錯誤提供錯誤提示及協助修改.
本文首先介紹枚舉類型的建立基本概念以及如何在Eclipse 3.1平台上建立枚舉類型,然後我們通過在Eclipse 3.1開發環境中的例子來說明枚舉類型的應用.
1.2 建立枚舉類型
下面的例子顯示了如何建立一個最基本的枚舉類型:
清單 1. 枚舉類型的定義
public enum Priority {High, Medium, Low };
|
它包括一個關鍵字enum ,一個新枚舉類型的名字 Priority 以及為Priority定義的一組值.
在Eclipse 3.1平台上,按照下面步驟來產生枚舉類型:(Eclipse 3.1提供了一個新的枚舉類型建立嚮導(wizard)以方便使用者建立枚舉類型)
1) File->New->Other, 模板列表顯示出來.
2) 在模板列表上選中 Java->Enum, 點擊 Next 按鈕
3) 按圖 1填寫每一個域 如下:
圖 1: Eclipse 3.1 枚舉類型建立模板
4) 點擊 Finish 按鈕, 產生Priority 的類(定義???), 並聲明Priority 的每一個值,如 2所示:(High, Medium, low從何而來???)
圖 2: 枚舉類型Priority
在建立枚舉類型時,注意幾個重要的概念.
所有建立的枚舉類型都擴充於 java.lang.Enum. Enum 是在J2SE 5.0 裡定義的一個新類, 它本身不是枚舉類型.在建立枚舉類型時,必須用enum 關鍵字,不能直接地定義一個繼承Enum的類來建立一個枚舉類型,儘管所有建立的枚舉類型實際上都是Enum 的子類. 如果直接繼承Enum, compiler 就會報錯(會導致編譯錯誤).3 所示
圖3. 直接繼承Enum 類
枚舉類型裡定義的每一個值都是枚舉類型的一個執行個體,比方說High是Priority的一個執行個體.枚舉類型又是擴充於Enum. 所以枚舉類型的每一個值聲明時, 預設時都將映射到Enum(String name, int ordinal) 建構函式中.換句話說,enum Priority {High, Medium, Low } 的實現是調用了下面的Enum 建構函式:
清單2 映射的建構函式調用
new Enum< Priority >("High", 0); new Enum< Priority >("Medium", 1); new Enum< Priority >("Low", 2);
|
每一個建立的枚舉類型都是Enum 的子類,除了上面調用父類 Enum 的建構函式外,枚舉類型可以使用參數為定義一些自己的建構函式.當聲明值時,只需調用此枚舉類型定義的建構函式,而且不必添加 new 關鍵字.在清單3裡, Priority 的一個執行個體產生,這個執行個體就是High (38).
清單3.其它建構函式調用
enum Priority { High (38), Medium(36.5), Low (5.2); double temperature; Priority (double p) temperature = p; }
|
另外要強調的兩點: 一是這些枚舉類型的建構函式都是私人的.它是不能被其它的類或者其它的枚舉類型調用的. 而且這個私人修飾符是由編譯器自動加的,如果我們定義這些建構函式時,在前面加上public 修飾符, 就會導致編譯錯誤, 如5所示. 二是變數定義必須在枚舉類型值定義之後. 中double temperature 必須在枚舉類型值定義完了(分號表示枚舉類型值定義完了, 如 Low(5.2);) 才能聲明.
圖4. 枚舉類型的建構函式是私人的
在J2SE 5.0以前,當我們實現一個枚舉類時,一般都是把一個整數關聯到此枚舉類的某一個值的名字,出現的問題是同一個整數可以代表不同枚舉類的值. 下面的例子裡定義兩個枚舉類 Course and Grade 如下:
清單4.
public class Course { public static final int EnglishLit = 1; public static final int Calculus = 2; public static final int MusicTheory = 3; public static final int MusicPerformance = 4; } public class Grade { public static final int A = 1; public static final int B = 2; public static final int C = 3; public static final int D = 4; public static final int F = 5; public static final int INCOMPLETE = 6; }
|
如果開發人員誤把student1.assignGrade(Grade.A)寫成student1.assignGrade(Course.EnglishList); 在編譯 階段是不能發現問題的,如果用J2SE 5.0 枚舉類型(enum)可以避免這些問題.
- 枚舉類型每一個值都是public, static and final的.也就是說,這些值是唯一的而且一旦定義了是不能被重寫或修改.而且儘管在枚舉類型每一個值聲明時沒有出現static關鍵字, 實際上值都是靜態, 而且我們不能在值前面加上static, public,final 修飾符,否則就會出現 6的錯誤.
圖5 枚舉類型值的錯誤聲明
- 枚舉類型都實現了java.lang.Comparable,枚舉類型的值是可以比較排序的,排列順序就是枚舉類型定義這些值的順序.
1.3 枚舉類型的應用
下面各小節介紹了枚舉類型的各種應用.
1.3.1迴圈(Iteration)
當我們寫程式時,常常遇到對數組或列表裡的每一個對象進行處理的情況.在J2SE 5.0以前,如果要在一個數組或列表裡進行輪循時,我們的做法比較繁瑣,需要藉助java.util.Iterator 類, 如下所示:
清單5:
List priorities = Priority.values().; for (Iterator iter = priorities.iterator(); iter.hasNext();) { Priority p = (Priority) iter.next(); process(p); }
|
現在我們可以通過J2SE 5.0 的for/in loop和枚舉類型一起使用. 這能使以前花很多時間寫的程式簡單化,如上面清單5的程式可簡化為:
清單6:
for (Priority g: Priority.values()){ process(g); }
|
我們把上面的虛擬碼寫成程式在Eclipse3.1上運行,如所示,在右下控制平台視圖裡顯示了運行結果.如果看不見控制平台,點擊Window->Other Views->Console, 控制平台就會出現在右下角.
圖6 枚舉類型在迴圈中的應用
我們在使用for/in loop 時要求它的運算式要求必須是數組或者是實現了java.lang.Iterable的集合,而枚舉類型的values()函數返回的就是一個數組.另外迴圈變數的聲明必須是在loop裡, 包括變數類型和變數名.
我們不能在迴圈裡使用一個在迴圈之外聲明的變數.這和J2SE 5.0以前for loop 裡用的迴圈變數的聲明不同.
1.3.2 轉換(Switch)
我們常用的一種判斷語句就是Switch-case 語句. 在Switch 語句中使用枚舉類型,不僅能簡化程式,而且增強了程式的可讀性.
清單8.
File1: Task.java public class Task { Priority myPriority; public Task (Priority p) { myPriority=p; } public Priority getPriority(){ return myPriority; }} File2: TestSwitch.java public class TestSwitch ( Task task = new Task(Priority.Medium); switch (task.getPriority( )) { case High: //do case High break; case Midum: // fall through to Low case Low: //do case Low break; default: throw new AssertionError("Unexpected enumerated value!"); } }
|
在Switch語句裡使用枚舉類型時,一定不能在每一個枚舉類型值的前面加上枚舉類型的類名,否則編譯器就會報錯(會導致編譯錯誤). 我們把上面的程式稍作修改,在case 語句裡加上枚舉類型的類名並運行在Eclipse 3.1 平台上. 我們發現Eclipse 的問題視圖裡提示case 語句裡枚舉類型值的前面加上枚舉類型的類名是錯誤的, 如8所示.
圖7: case 語句裡枚舉類型的值
原因是J2SE 5.0的實現要求case 語句裡每一個枚舉類型值是不能有枚舉類型類作為首碼的.前面談到過每一個枚舉類型的值都是枚舉類型的一個執行個體.那麼當編譯器編譯case語句時, 是如何處理這些執行個體的? 這有兩種情況:如果switch 與枚舉類型定義在同一個編譯單元, 第一次編譯時間一個新表會建立在記憶體裡. 在這個表裡, 每一個枚舉類型的值都和它在枚舉類型裡定義的順序關聯起來. 編譯器編譯結果就和下面清單9顯示的的程式很像.只不過順序號沒有加到程式裡, 而是編譯器在表裡快速查詢. 如果枚舉類型被修改或從定義,表會被更新.
清單 9:
public class TestSwitch ( Task task = new Task(); switch (task.getPriority( )) { case 0: //do case High break; case 1: // fall through to Low case 2: //do case Low break; default: throw new AssertionError("Unexpected enumerated value!"); } }
|
還有一種經常出現的情況是 switch 與枚舉類型定義不是在同一個編譯單元.在這種情況下, 大多數編譯器就會把switch-case 語句翻譯成一系列的if/else 語句:
清單 10:
Priority tmp = task.getPriority( ); if (tmp == High) //do case High else if (tmp == Midium) else if (tmp == Low) //do case Low else { throw new AssertionError("Unexpected enumerated value!"); }
|
1.3.3 Maps of Enum and Sets of Enum
在J2SE 5.0 的java.util 程式包中提供兩個新類:EnumMap 和 EnumSet,這兩個類與枚舉類型的結合應用可使以前非常繁瑣的程式變得簡單方便.EnumMap 類提供了java.util.Map 介面的一個特殊實現,該介面中的鍵(key)是一個枚舉類型.
清單 11:. EnumMap 例子
public void test() throws IOException { EnumMap<Priority, String> descriptionMessages = new EnumMap< Priority, String>( Priority.class); descriptionMessages.put(Priority.High, "High means ..."); descriptionMessages.put(Priority.Medium, " Medium represents..."); descriptionMessages.put(Priority.Low, " Low means..."); for (Priority p : Priority.values( ) ) { System.out.println("For priority " + p + ", decription is: " + descriptionMessages.get(p)); } }
|
EnumSet 類提供了 java.util.Set 介面的實現,該介面儲存了某種枚舉類型的值的集合.EnumSet的作用類似於特性的集合,或者類似於某個枚舉類型的所有元素的值的子集.EnumSet 類擁有一系列的靜態方法,可以用這些方法從枚舉類型中擷取單個元素或某些元素, 下面的程式例子顯示如何這些靜態方法:
清單 12:.EnumSet 例子
public class TestEnumSet { public enum ColorFeature { RED,BLUE, GREEN, YELLOW,BLACK } ; public static void main(String[] args) { EnumSet allFeatures = EnumSet.allOf(ColorFeature.class); EnumSet warmColorFeatures = EnumSet.of(ColorFeature.RED, ColorFeature.YELLOW); EnumSet non_warmColorFeatures = EnumSet.complementOf(warmColorFeatures); EnumSet notBlack = EnumSet.range(ColorFeature.RED, ColorFeature.YELLOW); for (ColorFeature cf : ColorFeature.values()){ if (warmColorFeatures.contains(cf)) { System.out.println("warmColor "+cf.name()); } if (non_warmColorFeatures.contains(cf)) { System.out.println("non_WarmColor "+cf.name()); } } } }
|
我們在Eclipse3.1環境中運行上面的程式,結果如:
圖8: EnumSet 範例運行結果
1.3.4 枚舉類型的函數定義
在介紹建立枚舉類型中曾提到枚舉類型都是java.lang.Enum的子類. 也就是說, 枚舉類型都是可編譯的Java 的類,那麼就可以在枚舉類型裡添加建構函式和其它函數,如清單13裡的getDescription()
清單 13:
public enum ColorFeature { RED(0), BLUE(0), GREEN(300), YELLOW(0), BLACK(0); /** The degree for each kind of color*/ private int degree; ColorFeatures(int degree) { this.degree = degree; } public int getDegree( ) { return degree; } public String getDescription( ) { switch(this) { case RED: return "the color is red"; case BLUE: return "the color is blue"; case GREEN: return "the color is green"; case BLACK: return "the color is black"; case YELLOW: return "the color is yellow" default: return "Unknown Color"; } }}
|
枚舉類型的函數定義的應用是很有用的, 例如可以讓多個枚舉類型實現同一個interface 來達到程式設計的模式化. 例如一個定義了getDescription ()介面的interface,讓有同樣需求的不同枚舉類型來實現它.上面的colorFeature 可以實現它, 另一個FontFeature也可以實現它.
1.3.5 特定於常量的類主體
在上一節裡提到枚舉類型可以定義自己的函數,其實更進一步,枚舉類型的每一個值都可以實現枚舉類型裡定義的抽象函數,這聽起來很不可思議,我們可以先看下面的例子.
public enum Priority implements Feature { High (38) { public void perform() { System.out.println("high 38"); } }, Medium(36.5) { public void perform() { System.out.println("medium 36.5"); } }, Low (5.2){ public void perform() { System.out.println("low 5.2"); } }; public abstract void perform(); public String getDescription(Priority p) { return null; } }
|
枚舉類型Priority 定義了一個抽象函數perform(),Priority的每一個值都對perform 函數實現了重載,這就是枚舉類型的特定於常量的類主體.在這種情況下,每聲明一個值,枚舉類型的一個子類產生,然後產生這個子類的唯一的執行個體來表示這個值.不同的值,就有對應的不同的子類.每個子類可以對父類的抽象函數進行重載.我們可以用下面的程式在Eclipse3.1環境中運行來證明此時3個子類產生.
public class Task { Priority myPriority; public Task (Priority p) { myPriority=p; } public Priority getPriority(){ return myPriority; } public void test() throws IOException { if (myPriority == Priority.High) System.out.println(Priority.High.getClass().getName()); if (myPriority == Priority.Medium) System.out.println(Priority.Medium.getClass().getName()); if (myPriority == Priority.Low) System.out.println(Priority.Low.getClass().getName()); }} public class TestSwitch { public static void main(String[] args) { Task task = new Task(Priority.High); Task task1 = new Task(Priority.Medium); Task task2 = new Task(Priority.Low); try { task.test(); task1.test(); task2.test(); } catch (IOException e) { e.printStackTrace(); } }
|
運行結果如10.
圖9 測試特定於常量的類主體運行結果
由於特定於常量的類主體難理解容易出錯,它的應用比較少,大多數情況下可以用switch-case 語句替代. 在這裡簡單介紹,僅供參考.
1.4 枚舉類型的小結
使用枚舉類型是很簡單的.它定義一個固定的、封閉的值集合,然後,在需要這些值中的某一個值時,可以通過它的名稱來指定它,這就是枚舉類型的簡單性.枚舉類型的值就是枚舉類型的執行個體,編譯器會確保沒有傳入其他的類型,這就是枚舉類型的安全性.這些枚舉類型就是類本身,因此,可以對類進行的所有操作同樣可以作用於枚舉類型上.我們要小心使用建構函式和函數重載方法,不要因為這些特性可用就任意使用它們.比如特定於常量的類主體,大多情況下可以用Switch語句來代替,更容易讓人理解而且不容易出錯.我們也看到了Eclipse 3.1平台對枚舉類型的支援,包括提供建立模板,錯誤資訊提示等.總之,枚舉類型的靈活應用能極大的方便和簡化了我們的開發工作。