本文將介紹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
在創建枚舉類型時,注意幾個重要的概念.
XMLns:xsi="http://www.w3.org/2001/XMLSchema-instance">
· 所有創建的枚舉類型都擴展於 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 枚舉類型的應用
下面各小節介紹了枚舉類型的各種應用.
XMLns:xsi="http://www.w3.org/2001/XMLSchema-instance">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平臺對枚舉類型的支持,包括提供創建模板,錯誤信息提示等.總之,枚舉類型的靈活應用能極大的方便和簡化了我們的開發工作。