本文將介紹J2SE 5.0中三個(gè)比較重要的特性: 枚舉類(lèi)型, 注釋類(lèi)型, 范型, 并在此基礎上介紹在如何在Eclipse 3.1開(kāi)發(fā)環(huán)境中開(kāi)發(fā)枚舉類(lèi)型, 注釋類(lèi)型和范型應用。
J2SE 5.0 (Tiger)的發(fā)布是Java語(yǔ)言發(fā)展史上的一個(gè)重要的里程碑, 是迄今為止在 Java 編程方面所取得的最大進(jìn)步。
J2SE 5.0提供了很多令人激動(dòng)的特性.這些特性包括范型(generics)的支持, 枚舉類(lèi)型(enumeration)的支持, 元數據(metadata)的支持, 自動(dòng)拆箱(unboxing)/裝箱(autoboxing), 可變個(gè)數參數(varargs), 靜態(tài)導入(static imports), 以及新的線(xiàn)程架構(Thread framework)。
隨著(zhù)J2SE 5.0的推出, 越來(lái)越多的集成開(kāi)發(fā)環(huán)境(IDE)支持J2SE 5.0的開(kāi)發(fā)。 著(zhù)名的開(kāi)源Java IDE Eclipse從3.1M4開(kāi)始支持J2SE 5.0的開(kāi)發(fā), 目前最新的版本是3.1RC4。
本系列將介紹J2SE 5.0中三個(gè)比較重要的特性: 枚舉類(lèi)型, 注釋類(lèi)型, 范型, 并在此基礎上介紹在如何在Eclipse 3.1開(kāi)發(fā)環(huán)境中開(kāi)發(fā)枚舉類(lèi)型, 注釋類(lèi)型和范型應用.本文將介紹枚舉類(lèi)型。
1. 枚舉類(lèi)型
1.1枚舉類(lèi)型簡(jiǎn)介
J2SE 5.0 以及之前的JDK有兩種基本方法可以來(lái)定義新類(lèi)型:通過(guò)Classes 以及Interface. 對于大部分面向對象編程來(lái)說(shuō),這兩種方法看起來(lái)似乎足夠了。但是在一些特殊情況下,這些方法就不適合.例如,我們想定義一個(gè)類(lèi)型 Priority, 它只能接受 High, Medium, Low 三種值。
其他任何值都是非法的。J2SE 5.0 以前的JDK是可以構造這種類(lèi)型的,但是需要做很多工作,有可能會(huì )帶來(lái)如不安全(類(lèi)型安全性問(wèn)題?)等潛在問(wèn)題,而J2SE 5.0的枚舉類(lèi)型(Enum) 能避免這些問(wèn)題。
Eclipse 是JAVA程序員最常用的開(kāi)發(fā)平臺,而Eclipse 3.1提供對J2SE 5.0的支持,它為J2SE 5.0的新功能提供了幫助工具。在對枚舉類(lèi)型的支持上,它不僅提供了枚舉類(lèi)型的創(chuàng )建模板,而且為枚舉類(lèi)型的各種開(kāi)發(fā)錯誤提供錯誤提示及幫助修改。
本文首先介紹枚舉類(lèi)型的創(chuàng )建基本概念以及如何在Eclipse 3.1平臺上創(chuàng )建枚舉類(lèi)型,然后我們通過(guò)在Eclipse 3.1開(kāi)發(fā)環(huán)境中的例子來(lái)說(shuō)明枚舉類(lèi)型的應用。
1.2 創(chuàng )建枚舉類(lèi)型
下面的例子顯示了如何創(chuàng )建一個(gè)最基本的枚舉類(lèi)型:
清單 1. 枚舉類(lèi)型的定義
public enum Priority {High, Medium, Low };
它包括一個(gè)關(guān)鍵字enum ,一個(gè)新枚舉類(lèi)型的名字 Priority 以及為Priority定義的一組值。
在Eclipse 3.1平臺上,按照下面步驟來(lái)生成枚舉類(lèi)型:(Eclipse 3.1提供了一個(gè)新的枚舉類(lèi)型創(chuàng )建向導(wizard)以方便用戶(hù)創(chuàng )建枚舉類(lèi)型)
1) File->New->Other, 模板列表顯示出來(lái)
2) 在模板列表上選中 Java->Enum, 點(diǎn)擊 Next 按鈕
3) 按圖 1填寫(xiě)每一個(gè)域 如下:
圖 1: Eclipse 3.1 枚舉類(lèi)型創(chuàng )建模板
4) 點(diǎn)擊 Finish 按鈕, 生成Priority 的類(lèi)(定義?), 并聲明Priority 的每一個(gè)值,如下圖 2所示:(High, Medium, low從何而來(lái)?)
圖 2: 枚舉類(lèi)型Priority
在創(chuàng )建枚舉類(lèi)型時(shí),注意幾個(gè)重要的概念.
所有創(chuàng )建的枚舉類(lèi)型都擴展于 java.lang.Enum. Enum 是在J2SE 5.0 里定義的一個(gè)新類(lèi), 它本身不是枚舉類(lèi)型。
在創(chuàng )建枚舉類(lèi)型時(shí),必須用enum 關(guān)鍵字,不能直接地定義一個(gè)繼承Enum的類(lèi)來(lái)創(chuàng )建一個(gè)枚舉類(lèi)型,盡管所有創(chuàng )建的枚舉類(lèi)型實(shí)際上都是Enum 的子類(lèi)。如果直接繼承Enum, compiler 就會(huì )報錯(會(huì )導致編譯錯誤)。如圖3 所示
圖3. 直接繼承Enum 類(lèi)
枚舉類(lèi)型里定義的每一個(gè)值都是枚舉類(lèi)型的一個(gè)實(shí)例,比方說(shuō)High是Priority的一個(gè)實(shí)例。枚舉類(lèi)型又是擴展于Enum. 所以枚舉類(lèi)型的每一個(gè)值聲明時(shí), 缺省時(shí)都將映射到Enum(String name, int ordinal) 構造函數中.換句話(huà)說(shuō),enum Priority {High, Medium, Low } 的實(shí)現是調用了下面的Enum 構造函數:
清單2 映射的構造函數調用
new Enum< Priority >("High", 0);new Enum< Priority >("Medium", 1);new Enum< Priority >("Low", 2);
每一個(gè)創(chuàng )建的枚舉類(lèi)型都是Enum 的子類(lèi),除了上面調用父類(lèi) Enum 的構造函數外,枚舉類(lèi)型可以使用參數為定義一些自己的構造函數。
當聲明值時(shí),只需調用此枚舉類(lèi)型定義的構造函數,而且不必添加 new 關(guān)鍵字.在清單3里, Priority 的一個(gè)實(shí)例生成,這個(gè)實(shí)例就是High (38)。
清單3.其它構造函數調用
enum Priority {High (38),Medium(36.5),Low (5.2);double temperature;Priority (double p) temperature = p;}
另外要強調的兩點(diǎn): 一是這些枚舉類(lèi)型的構造函數都是私有的。它是不能被其它的類(lèi)或者其它的枚舉類(lèi)型調用的。 而且這個(gè)私有修飾符是由編譯器自動(dòng)加的,如果我們定義這些構造函數時(shí),在前面加上public 修飾符, 就會(huì )導致編譯錯誤, 如下圖5所示。 二是變量定義必須在枚舉類(lèi)型值定義之后。
上圖中double temperature 必須在枚舉類(lèi)型值定義完了(分號表示枚舉類(lèi)型值定義完了, 如 Low(5.2);) 才能聲明。
圖4. 枚舉類(lèi)型的構造函數是私有的
在J2SE 5.0以前,當我們實(shí)現一個(gè)枚舉類(lèi)時(shí),一般都是把一個(gè)整數關(guān)聯(lián)到此枚舉類(lèi)的某一個(gè)值的名字,出現的問(wèn)題是同一個(gè)整數可以代表不同枚舉類(lèi)的值。下面的例子里定義兩個(gè)枚舉類(lèi) 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;}
如果開(kāi)發(fā)者誤把student1.assignGrade(Grade.A)寫(xiě)成student1.assignGrade(Course.EnglishList); 在編譯階段是不能發(fā)現問(wèn)題的,如果用J2SE 5.0 枚舉類(lèi)型(enum)可以避免這些問(wèn)題。
枚舉類(lèi)型每一個(gè)值都是public, static and final的.也就是說(shuō),這些值是唯一的而且一旦定義了是不能被重寫(xiě)或修改.而且盡管在枚舉類(lèi)型每一個(gè)值聲明時(shí)沒(méi)有出現static關(guān)鍵字, 實(shí)際上值都是靜態(tài)的, 而且我們不能在值前面加上static, public,final 修飾符,否則就會(huì )出現下圖 6的錯誤。
圖5 枚舉類(lèi)型值的錯誤聲明
枚舉類(lèi)型都實(shí)現了java.lang.Comparable,枚舉類(lèi)型的值是可以比較排序的,排列順序就是枚舉類(lèi)型定義這些值的順序。
1.3 枚舉類(lèi)型的應用
下面各小節介紹了枚舉類(lèi)型的各種應用
1.3.1循環(huán)(Iteration)
當我們寫(xiě)程序時(shí),常常遇到對數組或列表里的每一個(gè)對象進(jìn)行處理的情況。在J2SE 5.0以前,如果要在一個(gè)數組或列表里進(jìn)行輪循時(shí),我們的做法比較繁瑣,需要借助java.util.Iterator 類(lèi), 如下所示:
清單5:
List priorities = Priority.values().;for (Iterator iter = priorities.iterator(); iter.hasNext();) {Priority p = (Priority) iter.next();process(p);}
現在我們可以通過(guò)J2SE 5.0 的for/in loop和枚舉類(lèi)型一起使用. 這能使以前花很多時(shí)間寫(xiě)的程序簡(jiǎn)單化,如上面清單5的程序可簡(jiǎn)化為:
清單6:
for (Priority g: Priority.values()){process(g);}
我們把上面的偽代碼寫(xiě)成程序在Eclipse3.1上運行,如下圖所示,在右下控制平臺視圖里顯示了運行結果。如果看不見(jiàn)控制平臺,點(diǎn)擊Window->Other Views->Console, 控制平臺就會(huì )出現在右下角。
圖6 枚舉類(lèi)型在循環(huán)中的應用
我們在使用for/in loop 時(shí)要求它的表達式要求必須是數組或者是實(shí)現了java.lang.Iterable的集合,而枚舉類(lèi)型的values()函數返回的就是一個(gè)數組。另外循環(huán)變量的聲明必須是在loop里, 包括變量類(lèi)型和變量名。
我們不能在循環(huán)里使用一個(gè)在循環(huán)之外聲明的變量。這和J2SE 5.0以前for loop 里用的循環(huán)變量的聲明不同。
1.3.2 轉換(Switch)
我們常用的一種判斷語(yǔ)句就是Switch-case 語(yǔ)句。在Switch 語(yǔ)句中使用枚舉類(lèi)型,不僅能簡(jiǎn)化程序,而且增強了程序的可讀性。
清單8.
File1: Task.javapublic class Task {Priority myPriority;public Task (Priority p) {myPriority=p;}public Priority getPriority(){ return myPriority;}}File2: TestSwitch.javapublic class TestSwitch (Task task = new Task(Priority.Medium);switch (task.getPriority( )) {case High://do case Highbreak;case Midum: // fall through to Lowcase Low://do case Lowbreak;default: throw new AssertionError("Unexpected enumerated value!");} }
在Switch語(yǔ)句里使用枚舉類(lèi)型時(shí),一定不能在每一個(gè)枚舉類(lèi)型值的前面加上枚舉類(lèi)型的類(lèi)名,否則編譯器就會(huì )報錯(會(huì )導致編譯錯誤?)。
我們把上面的程序稍作修改,在case 語(yǔ)句里加上枚舉類(lèi)型的類(lèi)名并運行在Eclipse 3.1 平臺上. 我們發(fā)現Eclipse 的問(wèn)題視圖里提示case 語(yǔ)句里枚舉類(lèi)型值的前面加上枚舉類(lèi)型的類(lèi)名是錯誤的, 如下圖7所示
圖7: case 語(yǔ)句里枚舉類(lèi)型的值
原因是J2SE 5.0的實(shí)現要求case 語(yǔ)句里每一個(gè)枚舉類(lèi)型值是不能有枚舉類(lèi)型類(lèi)作為前綴的.前面談到過(guò)每一個(gè)枚舉類(lèi)型的值都是枚舉類(lèi)型的一個(gè)實(shí)例。那么當編譯器編譯case語(yǔ)句時(shí), 是如何處理這些實(shí)例的?
這有兩種情況:如果switch 與枚舉類(lèi)型定義在同一個(gè)編譯單元, 第一次編譯時(shí)一個(gè)新表會(huì )創(chuàng )建在內存里. 在這個(gè)表里, 每一個(gè)枚舉類(lèi)型的值都和它在枚舉類(lèi)型里定義的順序關(guān)聯(lián)起來(lái)。
編譯器編譯結果就和下面清單9顯示的的程序很像.只不過(guò)順序號沒(méi)有加到程序里, 而是編譯器在表里快速查詢(xún)。如果枚舉類(lèi)型被修改或從定義,表會(huì )被更新。
清單 9:
public class TestSwitch (Task task = new Task();switch (task.getPriority( )) {case 0://do case Highbreak;case 1: // fall through to Lowcase 2://do case Lowbreak;default: throw new AssertionError("Unexpected enumerated value!");} }
還有一種經(jīng)常出現的情況是 switch 與枚舉類(lèi)型定義不是在同一個(gè)編譯單元。在這種情況下, 大多數編譯器就會(huì )把switch-case 語(yǔ)句翻譯成一系列的if/else 語(yǔ)句:
清單 10:
Priority tmp = task.getPriority( );if (tmp == High)//do case Highelse if (tmp == Midium)else if (tmp == Low)//do case Lowelse {throw new AssertionError("Unexpected enumerated value!");}
1.3.3 Maps of Enum and Sets of Enum
在J2SE 5.0 的java.util 程序包中提供兩個(gè)新類(lèi):EnumMap 和 EnumSet,這兩個(gè)類(lèi)與枚舉類(lèi)型的結合應用可使以前非常繁瑣的程序變得簡(jiǎn)單方便。EnumMap 類(lèi)提供了java.util.Map 接口的一個(gè)特殊實(shí)現,該接口中的鍵(key)是一個(gè)枚舉類(lèi)型。
清單 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));}}
枚舉類(lèi)型的函數定義的應用是很有用的, 例如可以讓多個(gè)枚舉類(lèi)型實(shí)現同一個(gè)interface 來(lái)達到程序設計的模式化。例如一個(gè)定義了getDescription ()接口的interface,讓有同樣需求的不同枚舉類(lèi)型來(lái)實(shí)現它。上面的colorFeature 可以實(shí)現它, 另一個(gè)FontFeature也可以實(shí)現它。
1.3.5 特定于常量的類(lèi)主體
在上一節里提到枚舉類(lèi)型可以定義自己的函數,其實(shí)更進(jìn)一步,枚舉類(lèi)型的每一個(gè)值都可以實(shí)現枚舉類(lèi)型里定義的抽象函數,這聽(tīng)起來(lái)很不可思議,我們可以先看下面的例子。
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;}}
枚舉類(lèi)型Priority 定義了一個(gè)抽象函數perform(),Priority的每一個(gè)值都對perform 函數實(shí)現了重載,這就是枚舉類(lèi)型的特定于常量的類(lèi)主體.在這種情況下,每聲明一個(gè)值,枚舉類(lèi)型的一個(gè)子類(lèi)生成,然后生成這個(gè)子類(lèi)的唯一的實(shí)例來(lái)表示這個(gè)值.不同的值,就有對應的不同的子類(lèi).每個(gè)子類(lèi)可以對父類(lèi)的抽象函數進(jìn)行重載.我們可以用下面的程序在Eclipse3.1環(huán)境中運行來(lái)證明此時(shí)3個(gè)子類(lèi)生成.
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();}}
運行結果如下圖9
圖9 測試特定于常量的類(lèi)主體運行結果
由于特定于常量的類(lèi)主體難理解容易出錯,它的應用比較少,大多數情況下可以用switch-case 語(yǔ)句替代. 在這里簡(jiǎn)單介紹,僅供參考。
1.4 枚舉類(lèi)型的小結
使用枚舉類(lèi)型是很簡(jiǎn)單的.它定義一個(gè)固定的、封閉的值集合,然后,在需要這些值中的某一個(gè)值時(shí),可以通過(guò)它的名稱(chēng)來(lái)指定它,這就是枚舉類(lèi)型的簡(jiǎn)單性。枚舉類(lèi)型的值就是枚舉類(lèi)型的實(shí)例,編譯器會(huì )確保沒(méi)有傳入其他的類(lèi)型,這就是枚舉類(lèi)型的安全性。
這些枚舉類(lèi)型就是類(lèi)本身,因此,可以對類(lèi)進(jìn)行的所有操作同樣可以作用于枚舉類(lèi)型上.我們要小心使用構造函數和函數重載方法,不要因為這些特性可用就任意使用它們。
比如特定于常量的類(lèi)主體,大多情況下可以用Switch語(yǔ)句來(lái)代替,更容易讓人理解而且不容易出錯.我們也看到了Eclipse 3.1平臺對枚舉類(lèi)型的支持,包括提供創(chuàng )建模板,錯誤信息提示等.總之,枚舉類(lèi)型的靈活應用能極大的方便和簡(jiǎn)化了我們的開(kāi)發(fā)工作。
http://www.j2medev.com.cn/Article/ShowArticle.asp?ArticleID=648