1.1. 背景
J2SE(TM) 5.0正式發(fā)布至今已超過3個月的時間了,就在前不久,大概是在兩周之前,Sun又發(fā)布了更新過的JDK 5.0 Update 1,改掉了一些第一個版本中出現(xiàn)的bug。
由于Java社群等待這一從1.4向5.0版本升級已經(jīng)有相當(dāng)長的一段時間,大家都很關(guān)心5.0中有哪些值得關(guān)注的變化,于是blog的相關(guān)信息滿天飛,我也興沖沖地在自己的blog中添上了一系列的文章。無奈這些blog文章,包括我自己的在內(nèi),通常都是泛泛而談,因此CSDN第二期Java電子雜志的編輯們計劃做一個專題對這一話題與相關(guān)人士進行一番深入的探討。
作為這期電子刊物的一部分,編輯們也邀請我更系統(tǒng)的探討一下:J2SE(TM) 5.0中新引入的語言特性究竟在實際中有哪些用途,以及為什么要引入這些新特性。對此我深感榮幸。我本人很樂意將我的一些也許算得上經(jīng)驗的Java經(jīng)驗跟大家分享,希望這一篇小文能對大家了解J2SE(TM) 5.0有一定幫助。
1.2. 準(zhǔn)備工作
首先,為了了解J2SE(TM) 5.0的新的語言特性,你需要下載新版的JDK,在這里可以找到下載鏈接:http://java.sun.com/j2se/1.5.0/download.jsp。當(dāng)然,如果你已經(jīng)有過手動配置Java環(huán)境的經(jīng)歷,我也建議你使用一個支持J2SE(TM) 5.0的IDE,推薦Eclipse SDK 3.1 M4,或者NetBeans IDE 4.0。兩個都是開源免費的,且很容易找到(Eclipse不用說了,NetBeans IDE 4.0有與JDK 5.0 Update 1的捆綁版)。
說點題外話,Java的版本號自從1.2開始,似乎就多少顯得有點蹩腳。從1.2版本開始,Java (J2SE)被稱作Java 2,而不是Java 1.2,現(xiàn)在則顯得更加離奇:Java(TM) 2 Platform Standard Edition 5.0或者J2SE(TM) 5.0,而內(nèi)部的版本號還是1.5.0。那么到底是1、2、還是5呢?來看看Sun官方網(wǎng)站是怎么說的:
從Java誕生至今已有9年時間,而從第二代Java平臺J2SE算起也有5個年頭了。在這樣的背景下,將下一個版本的版本號從1.5改為5.0可以更好的反映出新版J2SE的成熟度、穩(wěn)定性、可伸縮性和安全性。
好吧,現(xiàn)在我們將面對如下一些名稱,而它們指的基本上是同一個東西:
l Tiger
l Java(TM) 2 Platform Standard Edition 5.0
l J2SE(TM) 5.0
l Java version 1.5.0
l …
在本文中,為了方便起見,我將統(tǒng)一使用J2SE(TM) 5.0這個名稱。
如果你對Java各個版本的代號感興趣,就像這里的"Tiger",可以參考如下網(wǎng)址:1.3. 概述
J2SE(TM) 5.0引入了很多激進的語言元素變化,這些變化或多或少減輕了我們開發(fā)人員的一些編碼負(fù)擔(dān),其中的大部分也必然會被應(yīng)用到即將發(fā)布的J2EE(TM) 5.0中。主要的新特性包括:
l 泛型
l 增強的for循環(huán)
l 自動裝箱和自動拆箱
l 類型安全的枚舉
l 可變長度參數(shù)
l 靜態(tài)引入
l 元數(shù)據(jù)(注解)
l C風(fēng)格的格式化輸出
這當(dāng)中,泛型、枚舉和注解可能會占用較大的篇幅,而其余的因為用法直截了當(dāng),抑或相對簡單,我就稍作介紹,剩下的留給讀者去思考、去探索了。
1.4. 泛型
泛型這個題目相當(dāng)大,大到完全可以就這個話題寫一本書。有關(guān)Java是否需要泛型和如何實現(xiàn)泛型的討論也早就在Java社群廣為流傳。終于,我們在J2SE(TM) 5.0中看到了它。也許目前Java對泛型的支持還算不上足夠理想,但這一特性的添加也經(jīng)足以讓我們欣喜一陣了。
在接下來的介紹中,我們會了解到:Java的泛型雖然跟C++的泛型看上去十分相似,但其實有著相當(dāng)大的區(qū)別,有些細(xì)節(jié)的東西也相當(dāng)復(fù)雜(至少很多地方會跟我們的直覺背道而馳)。可以這樣說,泛型的引入在很大程度上增加了Java語言的復(fù)雜度,對初學(xué)者尤其是個挑戰(zhàn)。下面我們將一點一點往里挖。
首先我們來看一個簡單的使用泛型類的例子:
ArrayList<Integer> aList = new ArrayList<Integer>();
aList.add(new Integer(1));
// ...
Integer myInteger = aList.get(0);
我們可以看到,在這個簡單的例子中,我們在定義aList的時候指明了它是一個直接受Integer類型的ArrayList,當(dāng)我們調(diào)用aList.get(0)時,我們已經(jīng)不再需要先顯式的將結(jié)果轉(zhuǎn)換成Integer,然后再賦值給myInteger了。而這一步在早先的Java版本中是必須的。也許你在想,在使用Collection時節(jié)約一些類型轉(zhuǎn)換就是Java泛型的全部嗎?遠(yuǎn)不止。單就這個例子而言,泛型至少還有一個更大的好處,那就是使用了泛型的容器類變得更加健壯:早先,Collection接口的get()和Iterator接口的next()方法都只能返回Object類型的結(jié)果,我們可以把這個結(jié)果強制轉(zhuǎn)換成任何Object的子類,而不會有任何編譯期的錯誤,但這顯然很可能帶來嚴(yán)重的運行期錯誤,因為在代碼中確定從某個Collection中取出的是什么類型的對象完全是調(diào)用者自己說了算,而調(diào)用者也許并不清楚放進Collection的對象具體是什么類的;就算知道放進去的對象“應(yīng)該”是什么類,也不能保證放到Collection的對象就一定是那個類的實例?,F(xiàn)在有了泛型,只要我們定義的時候指明該Collection接受哪種類型的對象,編譯器可以幫我們避免類似的問題溜到產(chǎn)品中。我們在實際工作中其實已經(jīng)看到了太多的ClassCastException,不是嗎?
泛型的使用從這個例子看也是相當(dāng)易懂。我們在定義ArrayList時,通過類名后面的<>括號中的值指定這個ArrayList接受的對象類型。在編譯的時候,這個ArrayList會被處理成只接受該類或其子類的對象,于是任何試圖將其他類型的對象添加進來的語句都會被編譯器拒絕。
那么泛型是怎樣定義的呢?看看下面這一段示例代碼:(其中用E代替在實際中將會使用的類名,當(dāng)然你也可以使用別的名稱,習(xí)慣上在這里使用大寫的E,表示Collection的元素。)
public class TestGenerics<E> {
Collection<E> col;
public void doSth(E elem) {
col.add(elem);
// ...
}
}
在泛型的使用中,有一個很容易有的誤解,那就是既然Integer是從Object派生出來的,那么ArrayList<Integer>當(dāng)然就是ArrayList<Object>的子類。真的是這樣嗎?我們仔細(xì)想一想就會發(fā)現(xiàn)這樣做可能會帶來的問題:如果我們可以把ArrayList<Integer>向上轉(zhuǎn)型為ArrayList<Object>,那么在往這個轉(zhuǎn)了型以后的ArrayList中添加對象的時候,我們豈不是可以添加任何類型的對象(因為Object是所有對象的公共父類)?這顯然讓我們的ArrayList<Integer>失去了原本的目的。于是Java編譯器禁止我們這樣做。那既然是這樣,ArrayList<Integer>以及ArrayList<String>、ArrayList<Double>等等有沒有公共的父類呢?有,那就是ArrayList<?>。?在這里叫做通配符。我們?yōu)榱丝s小通配符所指代的范圍,通常也需要這樣寫:ArrayList<? extends SomeClass>,這樣寫的含義是定義這樣一個類ArrayList,比方說SomeClass有SomeExtendedClass1和SomeExtendedClass2這兩個子類,那么ArrayList<? extends SomeClass>就是如下幾個類的父類:ArrayList<SomeClass>、ArrayList<SomeExtendedClass1>和ArrayList<SomeExtendedClass2>。
接下來我們更進一步:既然ArrayList<? extends SomeClass>是一個通配的公用父類,那么我們可不可以往聲明為ArrayList<? extends SomeClass>的ArrayList實例中添加一個SomeExtendedClass1的對象呢?答案是不能。甚至你不能添加任何對象。為什么?因為ArrayList<? extends SomeClass>實際上代表了所有ArrayList<SomeClass>、ArrayList<SomeExtendedClass1>和ArrayList<SomeExtendedClass2>三種ArrayList,甚至包括未知的接受SomeClass其他子類對象的ArrayList。我們拿到一個定義為ArrayList<? extends SomeClass>的ArrayList的時候,我們并不能確定這個ArrayList具體是使用哪個類作為參數(shù)定義的,因此編譯器也無法讓這段代碼編譯通過。舉例來講,如果我們想往這個ArrayList中放一個SomeExtendedClass2的對象,我們?nèi)绾伪WC它實際上不是其他的如ArrayList<SomeExtendedClass1>,而就是這個ArrayList<SomeExtendedClass2>呢?(還記得嗎?ArrayList<Integer>并非ArrayList<Object>的子類。)怎么辦?我們需要使用泛型方法。泛型方法的定義類似下面的例子:
public static <T extends SomeClass> void add (Collection<T> c, T elem) {
c.add(elem);
}
其中T代表了我們這個方法期待的那個最終的具體的類,相關(guān)的聲明必須放在方法簽名中緊靠返回類型的位置之前。在本例中,它可以是SomeClass或者SomeClass的任何子類,其說明<T entends SomeClass>放在void關(guān)鍵字之前(只能放在這里)。這樣我們就可以讓編譯器確信當(dāng)我們試圖添加一個元素到泛型的ArrayList實例中時,可以保證類型安全。
Java泛型的最大特點在于它是在語言級別實現(xiàn)的,區(qū)別于C# 2.0中的CLR級別。這樣的做法使得JRE可以不必做大的調(diào)整,缺點是無法支持一些運行時的類型甄別。一旦編譯,它就被寫死了,能提供的動態(tài)能力相當(dāng)弱。
個人認(rèn)為泛型是這次J2SE(TM) 5.0中引入的最重要的語言元素,給Java語言帶來的影響也是最大。舉個例子來講,我們可以看到,幾乎所有的Collections API都被更新成支持泛型的版本。這樣做帶來的好處是顯而易見的,那就是減少代碼重復(fù)(不需要提供多個版本的某一個類或者接口以支持不同類的對象)以及增強代碼的健壯性(編譯期的類型安全檢查)。不過如何才能真正利用好這個特性,尤其是如何實現(xiàn)自己的泛型接口或類供他人使用,就并非那么顯而易見了。讓我們一起在使用中慢慢積累。
1.5. 增強的for循環(huán)
你是否已經(jīng)厭倦了每次寫for循環(huán)時都要寫上那些機械的代碼,尤其當(dāng)你需要遍歷數(shù)組或者Collection,如:(假設(shè)在Collection中儲存的對象是String類型的)
public void showAll (Collection c) {
for (Iterator iter = c.iterator(); iter.hasNext(); ) {
System.out.println((String) iter.next());
}
}
public void showAll (String[] sa) {
for (int i = 0; i < sa.length; i++) {
System.out.println(sa[i]);
}
}
這樣的代碼不僅顯得臃腫,而且容易出錯,我想我們大家在剛開始接觸編程時,尤其是C/C++和Java,可能多少都犯過以下類似錯誤的一種或幾種:把for語句的三個表達式順序弄錯;第二個表達式邏輯判斷不正確(漏掉一些、多出一些、甚至死循環(huán));忘記移動游標(biāo);在循環(huán)體內(nèi)不小心改變了游標(biāo)的位置等等。為什么不能讓編譯器幫我們處理這些細(xì)節(jié)呢?在5.0中,我們可以這樣寫:
public void showAll (Collection c) {
for (Object obj : c) {
System.out.println((String) obj);
}
}
public void showAll (String[] sa) {
for (String str : sa) {
System.out.println(str);
}
}
這樣的代碼顯得更加清晰和簡潔,不是嗎?具體的語法很簡單:使用":"分隔開,前面的部分寫明從數(shù)組或Collection中將要取出的類型,以及使用的臨時變量的名字,后面的部分寫上數(shù)組或者Collection的引用。加上泛型,我們甚至可以把第一個方法變得更加漂亮:
public void showAll (Collection<String> cs) {
for (String str : cs) {
System.out.println(str);
}
}
有沒有發(fā)現(xiàn):當(dāng)你需要將Collection<String>替換成String[],你所需要做的僅僅是簡單的把參數(shù)類型"Collection<String>"替換成"String[]",反過來也是一樣,你不完全需要改其他的東西。這在J2SE(TM) 5.0之前是無法想象的。
對于這個看上去相當(dāng)方便的新語言元素,當(dāng)你需要在循環(huán)體中訪問游標(biāo)的時候,會顯得很別扭:比方說,當(dāng)我們處理一個鏈表,需要更新其中某一個元素,或者刪除某個元素等等。這個時候,你無法在循環(huán)體內(nèi)獲得你需要的游標(biāo)信息,于是需要回退到原先的做法。不過,有了泛型和增強的for循環(huán),我們在大多數(shù)情況下已經(jīng)不用去操心那些煩人的for循環(huán)的表達式和嵌套了。畢竟,我們大部分時間都不會需要去了解游標(biāo)的具體位置,我們只需要遍歷數(shù)組或Collection,對吧?
1.6. 自動裝箱/自動拆箱
所謂裝箱,就是把值類型用它們相對應(yīng)的引用類型包起來,使它們可以具有對象的特質(zhì),如我們可以把int型包裝成Integer類的對象,或者把double包裝成Double,等等。所謂拆箱,就是跟裝箱的方向相反,將Integer及Double這樣的引用類型的對象重新簡化為值類型的數(shù)據(jù)。
在J2SE(TM) 5.0發(fā)布之前,我們只能手工的處理裝箱和拆箱。也許你會問,為什么需要裝箱和拆箱?比方說當(dāng)我們試圖將一個值類型的數(shù)據(jù)添加到一個Collection中時,就需要先把它裝箱,因為Collection的add()方法只接受對象;而當(dāng)我們需要在稍后將這條數(shù)據(jù)取出來,而又希望使用它對應(yīng)的值類型進行操作時,我們又需要將它拆箱成值類型的版本?,F(xiàn)在,編譯器可以幫我們自動地完成這些必要的步驟。下面的代碼我提供兩個版本的裝箱和拆箱,一個版本使用手工的方式,另一個版本則把這些顯而易見的代碼交給編譯器去完成:
public static void manualBoxingUnboxing(int i) {
ArrayList<Integer> aList = new ArrayList<Integer>();
aList.add(0, new Integer(i));
int a = aList.get(0).intValue();
System.out.println("The value of i is " + a);
}
public static void autoBoxingUnboxing(int i) {
ArrayList<Integer> aList = new ArrayList<Integer>();
aList.add(0, i);
int a = aList.get(0);
System.out.println("The value of i is " + a);
}
看到了吧,在J2SE(TM) 5.0中,我們不再需要顯式的去將一個值類型的數(shù)據(jù)轉(zhuǎn)換成相應(yīng)的對象,從而把它作為對象傳給其他方法,也不必手工的將那個代表一個數(shù)值的對象拆箱為相應(yīng)的值類型數(shù)據(jù),只要你提供的信息足夠讓編譯器確信這些裝箱/拆箱后的類型在使用時是合法的:比方講,如果在上面的代碼中,如果我們使用的不是ArrayList<Integer>而是ArrayList或者其他不兼容的版本如ArrayList<java.util.Date>,會有編譯錯誤。
當(dāng)然,你需要足夠重視的是:一方面,對于值類型和引用類型,在資源的占用上有相當(dāng)大的區(qū)別;另一方面,裝箱和拆箱會帶來額外的開銷。在使用這一方便特性的同時,請不要忘記了背后隱藏的這些也許會影響性能的因素。
1.7. 類型安全的枚舉
在介紹J2SE(TM) 5.0中引入的類型安全枚舉的用法之前,我想先簡單介紹一下這一話題的背景。
我們知道,在C中,我們可以定義枚舉類型來使用別名代替一個集合中的不同元素,通常是用于描述那些可以歸為一類,而又具備有限數(shù)量的類別或者概念,如月份、顏色、撲克牌、太陽系的行星、五大洲、四大洋、季節(jié)、學(xué)科、四則運算符,等等。它們通??瓷先ナ沁@個樣子:
typedef enum {SPRING, SUMMER, AUTUMN, WINTER} season;
實質(zhì)上,這些別名被處理成int常量,比如0代表SPRING,1代表SUMMER,以此類推。因為這些別名最終就是int,于是你可以對它們進行四則運算,這就造成了語意上的不明確。
Java一開始并沒有考慮引入枚舉的概念,也許是出于保持Java語言簡潔的考慮,但是使用Java的廣大開發(fā)者對于枚舉的需求并沒有因為Java本身沒有提供而消失,于是出現(xiàn)了一些常見的適用于Java的枚舉設(shè)計模式,如int enum和typesafe enum,還有不少開源的枚舉API和不開源的內(nèi)部實現(xiàn)。
我大致說一下int enum模式和typesafe enum模式。所謂int enum模式就是模仿C中對enum的實現(xiàn),如:
public class Season {
public static final int SPRING = 0;
public static final int SUMMER = 1;
public static final int AUTUMN = 2;
public static final int WINTER = 3;
}
這種模式跟C中的枚舉沒有太多本質(zhì)上的區(qū)別,C枚舉的局限它基本上也有。而typesafe enum模式則要顯得健壯得多:
public class Season {
private final String name;
private Season(String name) {
this.name = name;
}
public String toString() {
return name;
}
public static final Season SPRING = new Season("spring");
public static final Season SUMMER = new Season("summer");
public static final Season AUTUMN = new Season("autumn");
public static final Season WINTER = new Season("winter");
}
后一種實現(xiàn)首先通過私有的構(gòu)造方法阻止了對該類的繼承和顯式實例化,因而我們只可能取得定義好的四種Season類別,并且提供了方便的toString()方法獲取有意義的說明,而且由于這是一個完全意義上的類,所以我們可以很方便的加入自己的方法和邏輯來自定義我們的枚舉類。
最終,Java決定擁抱枚舉,在J2SE(TM) 5.0中,我們看到了這一變化,它所采用的設(shè)計思路基本上就是上面提到的typesafe enum模式。它的語法很簡單,用一個實際的例子來說,要定義一個枚舉,我們可以這樣寫:
public enum Language {CHINESE, ENGLISH, FRENCH, HUNGARIAN}
接下來我們就可以通過Language.ENGLISH來使用了。呃…這個例子是不是有點太小兒科了,我們來看一個復(fù)雜點的例子。使用Java的類型安全枚舉,我們可以為所有枚舉元素定義公用的接口,然后具體到每個元素本身,可以針對這些接口實現(xiàn)一些特定的行為。這對于那些可以歸為一類,又希望能通過統(tǒng)一的接口訪問的不同操作,將會相當(dāng)方便。通常,為了實現(xiàn)類似的功能,我們需要自己來維護一套繼承關(guān)系或者類似的枚舉模式。這里借用Java官方網(wǎng)站上的一個例子:
public enum Operation {
PLUS { double eval(double x, double y) { return x + y; } },
MINUS { double eval(double x, double y) { return x - y; } },
TIMES { double eval(double x, double y) { return x * y; } },
DIVIDE { double eval(double x, double y) { return x / y; } };
// Do arithmetic op represented by this constant
abstract double eval(double x, double y);
}
在這個枚舉中,我們定義了四個元素,分別對應(yīng)加減乘除四則運算,對于每一種運算,我們都可以調(diào)用eval()方法,而具體的方法實現(xiàn)各異。我們可以通過下面的代碼來試驗上面這個枚舉類:
public static void main(String args[]) {
double x = Double.parseDouble(args[0]);
double y = Double.parseDouble(args[1]);
for (Operation op : Operation.values()) {
System.out.println(x + " " + op + " " + y + " = " + op.eval(x, y));
}
}
怎么樣,使用枚舉,我們是不是能夠很方便的實現(xiàn)一些有趣的功能?其實說穿了,Java的類型安全枚舉就是包含了有限數(shù)量的已生成好的自身實例的一種類,這些現(xiàn)成的實例可以通過類的靜態(tài)字段來獲取。
1.8. 可變長度參數(shù)
顧名思義,可變長度參數(shù)就是指在方法的參數(shù)體中,只要定義恰當(dāng),我們可以使用任意數(shù)量的參數(shù),類似于使用數(shù)組。在J2SE(TM) 5.0中,一個新的語法被引入,就是在參數(shù)類型名稱后面加上"...",表示該方法可以接受多個該類型的參數(shù)。需要說明的是可變長度參數(shù)必須放在參數(shù)列表的最后,且一個方法只能包含一個這樣的參數(shù)。在方法體內(nèi)部,這樣的參數(shù)被當(dāng)作數(shù)組處理,看上去代碼應(yīng)該類似這個樣子:
public String testVararg(String... args) {
StringBuilder sb = new StringBuilder();
for (String str : args) {
sb.append(str);
}
return sb.toString();
}
這樣的方法簽名跟你寫成testVararg(String[] args)的區(qū)別在于:在調(diào)用時,你不再需要傳入一個包裝好的String數(shù)組,你只需要簡單的寫一連串String參數(shù),以逗號隔開即可,就如同這個方法正好有一個重載的版本是接受那么多個String參數(shù)一樣。
1.9. 靜態(tài)引入
所謂靜態(tài)引入就是指除了引入類之外,我們現(xiàn)在又多了一種選擇:引入某個類的靜態(tài)字段。如:
import static java.lang.Math.PI;
或者
import static java.lang.Math.*;
這樣我們在接下來的代碼中,當(dāng)我們需要使用某個被引入的靜態(tài)字段時,就不用再寫上前面的類名了。當(dāng)然,出現(xiàn)名字沖突時,跟原來的類引入一樣,還是需要前綴以示區(qū)分。我個人認(rèn)為這個新語言元素意義不大。當(dāng)引入太多靜態(tài)字段后,代碼會變得難以閱讀和維護。由于靜態(tài)字段的名字通常不如類名那么具有描述性,我認(rèn)為原先在靜態(tài)字段前寫上類名才是更好的選擇。不過,畢竟每個人的喜好和需求不同,如果你覺得它對你有用,既然提供了,那么就用咯。
1.10. 元數(shù)據(jù)(注解)
注解是J2SE(TM) 5.0引入的重要語言元素,它所對應(yīng)的JSR是JSR 175,我們先來看看JSR 175的文檔對注解的說明:
注解不會直接影響程序的語義,而開發(fā)和部署工具則可以讀取這些注解信息,并作相應(yīng)處理,如生成額外的Java源代碼、XML文檔、或者其他將與包含注解的程序一起使用的物件。
在之前的J2SE版本中,我們已經(jīng)使用到了一部分早期的注解元素,如@deprecated等。這些元素通常被用于產(chǎn)生HTML的Javadoc。在J2SE(TM) 5.0中,注解被正式引入,且推到了Java歷史上前所未有的高度。
現(xiàn)在,注解不僅僅被用來產(chǎn)生Javadoc,更重要的,注解使得代碼的編譯期檢查更加有效和方便,同時也增強了代碼的描述能力。有一些注解是隨著J2SE(TM) 5.0一起發(fā)布的,我們可以直接使用。除此之外,我們也可以很方便的實現(xiàn)自定義的注解。在此基礎(chǔ)上,很多以前我們只能靠反射機制來完成的功能也變得更加容易實現(xiàn)。
我們來看現(xiàn)成的有哪些有用的注解:
首先是@Override,這個注解被使用在方法上,表明這個方法是從其父類繼承下來的,這樣的寫法可以很方便的避免我們在重寫繼承下來的方法時,不至于不小心寫錯了方法簽名,且悄悄的溜過了編譯器,造成隱蔽性相當(dāng)高的bug。
其次是@Deprecated,表明該項(類、字段、方法)不再被推薦使用。
還有一個@SuppressWarnings,表明該項(類、字段、方法)所涵蓋的范圍不需要顯示所有的警告信息。這個注解需要提供參數(shù),如unchecked等等。
下面我通過一個例子向大家說明這些現(xiàn)成的注解的用法:
public class Main {
@Deprecated
public String str;
public static void main(String[] args) {
new SubMain().doSomething();
}
public void doSomething() {
System.out.println("Done.");
}
}
class SubMain extends Main {
@Override
@SuppressWarnings("unchecked", "warning")
public void doSomething() {
java.util.ArrayList aList = new java.util.ArrayList();
aList.add(new Integer(0));
System.out.println("Done by SubMain.");
}
}
當(dāng)然,我們也完全可以寫自己的注解。注解定義的語法是@interface關(guān)鍵字。J2SE(TM) 5.0支持三種形式的注解:不帶參數(shù)的標(biāo)記注解、帶一個參數(shù)的注解和帶多個參數(shù)的完整注解。下面分別舉例說明:
標(biāo)記注解,類似@Deprecated,如:
@interface SomeEmptyAnnotation {}
單個參數(shù)的注解,如:
@interface MySingleElementAnnotation {
String value();
}
以及多個參數(shù)的注解,如:
@interface MyAnnotationForMethods {
int index();
String info();
String developer() default "Sean GAO";
}
我們可以看到,注解的定義跟interface的定義相當(dāng)類似,我們還可以指定默認(rèn)值。對于這些注解,我們也可以為其添加注解,所謂“注解的注解”。比方講,我們通常會使用@Target指定注解的作用對象,以及用@Retention指定注解信息寫入的級別,如源代碼、類文件等等。舉個例子:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface SignedMethod {
}
在使用時,我們需要在注解名稱前面寫上@,然后()中指定參數(shù)值,如:
@MyAnnotationForMethods (
index = 1,
info = "This is a method to test MyAnnotation.",
developer = "Somebody else"
)
public void testMethod1() {
// ...
}
注解的最大作用在于它在源代碼的基礎(chǔ)上增加了有用的信息,使得源代碼的描述性更強。這些信息可以被代碼之外的工具識別,從而可以很方便的增加外部功能,以及減少不必要的相關(guān)代碼/文件的維護。這里我想簡單提一個超出J2SE(TM) 5.0范疇的話題:在未來的EJB 3.0規(guī)范中會有相當(dāng)多的對注解的應(yīng)用,讓我們預(yù)覽一下將來的無狀態(tài)會話bean用注解來定義會是什么樣子:
@Stateless public class BookShelfManagerBean {
public void addBook(Book aBook) {
// business logic goes here...
}
public Collection getAllBooks() {
// business logic goes here...
}
// ...
}
我們甚至不用寫任何接口和部署描述符,這些工作將完全由外部工具通過讀取注解加上反射來完成,這不是很好嗎?
1.11. C風(fēng)格格式化輸出
Java總算也有類似C的printf()風(fēng)格的方法了,方法名同樣叫作printf(),這一特性依賴于前邊提到的可變長度參數(shù)。舉個例子來說,我們現(xiàn)在可以寫:
System.out.printf("%s has a value of %d.%n", someString, a);
怎么樣,看上去還不錯吧?需要注意的是Java為了支持多平臺,新增了%n標(biāo)示符,作為對\n的補充。有關(guān)Java格式化輸出的具體語法,請參考java.util.Formatter的API文檔。
1.12. 結(jié)語
在這一篇介紹性的文章中,我們一起領(lǐng)略了J2SE 5.0帶來的新的語言元素,不知道大家是否也跟筆者一樣,感受到了這些新特性在提高我們的開發(fā)效率上所作的巨大努力。其實不只是語言元素,J2SE(TM) 5.0的發(fā)布在其他很多方面都作了不小的改進,包括虛擬機、新的API類庫等等,性能和功能上都有大幅提升。
對于主要靠J2EE吃飯的朋友來講,也許真正意義上要在工作中充分利用這些新的元素,恐怕要等主流的J2EE服務(wù)器都支持J2EE(TM) 5.0的那一天了,對此我充滿期待。