轉自:http://www.window07.com/dev/code/java/advanced/2006-3-5/k80708.htm

1.1.????????背景
J2SE(TM)?5.0正式發布至今已超過3個月的時間了,就在前不久,大概是在兩周之前,Sun又發布了更新過的JDK?5.0?Update?1,改掉了一些第一個版本中出現的bug。

?

由于Java社群等待這一從1.4向5.0版本升級已經有相當長的一段時間,大家都很關心5.0中有哪些值得關注的變化,于是blog的相關信息滿天飛,我也興沖沖地在自己的blog中添上了一系列的文章。無奈這些blog文章,包括我自己的在內,通常都是泛泛而談,因此CSDN第二期Java電子雜志的編輯們計劃做一個專題對這一話題與相關人士進行一番深入的探討。

?

作為這期電子刊物的一部分,編輯們也邀請我更系統的探討一下:J2SE(TM)?5.0中新引入的語言特性究竟在實際中有哪些用途,以及為什么要引入這些新特性。對此我深感榮幸。我本人很樂意將我的一些也許算得上經驗的Java經驗跟大家分享,希望這一篇小文能對大家了解J2SE(TM)?5.0有一定幫助。

1.2.????????準備工作
首先,為了了解J2SE(TM)?5.0的新的語言特性,你需要下載新版的JDK,在這里可以找到下載鏈接:java.sun.com/j2se/1.5.0/download.jsp">http://java.sun.com/j2se/1.5.0/download.jsp。當然,如果你已經有過手動配置Java環境的經歷,我也建議你使用一個支持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,現在則顯得更加離奇:Java(TM)?2?Platform?Standard?Edition?5.0或者J2SE(TM)?5.0,而內部的版本號還是1.5.0。那么到底是1、2、還是5呢?來看看Sun官方網站是怎么說的:

?

從Java誕生至今已有9年時間,而從第二代Java平臺J2SE算起也有5個年頭了。在這樣的背景下,將下一個版本的版本號從1.5改為5.0可以更好的反映出新版J2SE的成熟度、穩定性、可伸縮性和安全性。

?

好吧,現在我們將面對如下一些名稱,而它們指的基本上是同一個東西:

l?????????Tiger

l?????????Java(TM)?2?Platform?Standard?Edition?5.0

l?????????J2SE(TM)?5.0

l?????????Java?version?1.5.0

l?????????…

在本文中,為了方便起見,我將統一使用J2SE(TM)?5.0這個名稱。

?

如果你對Java各個版本的代號感興趣,就像這里的"Tiger",可以參考如下網址:java.sun.com/j2se/codenames.html">http://java.sun.com/j2se/codenames.html。透露一點:Java下一個版本(6.0)的代號是"Mustang"野馬,再下一個版本(7.0)的代號是"Dolphin"海豚。

1.3.????????概述
J2SE(TM)?5.0引入了很多激進的語言元素變化,這些變化或多或少減輕了我們開發人員的一些編碼負擔,其中的大部分也必然會被應用到即將發布的J2EE(TM)?5.0中。主要的新特性包括:

l?????????泛型

l?????????增強的for循環

l?????????自動裝箱和自動拆箱

l?????????類型安全的枚舉

l?????????可變長度參數

l?????????靜態引入

l?????????元數據(注解)

l?????????C風格的格式化輸出

?

這當中,泛型、枚舉和注解可能會占用較大的篇幅,而其余的因為用法直截了當,抑或相對簡單,我就稍作介紹,剩下的留給讀者去思考、去探索了。

1.4.????????泛型
泛型這個題目相當大,大到完全可以就這個話題寫一本書。有關Java是否需要泛型和如何實現泛型的討論也早就在Java社群廣為流傳。終于,我們在J2SE(TM)?5.0中看到了它。也許目前Java對泛型的支持還算不上足夠理想,但這一特性的添加也經足以讓我們欣喜一陣了。

?

在接下來的介紹中,我們會了解到:Java的泛型雖然跟C++的泛型看上去十分相似,但其實有著相當大的區別,有些細節的東西也相當復雜(至少很多地方會跟我們的直覺背道而馳)。可以這樣說,泛型的引入在很大程度上增加了Java語言的復雜度,對初學者尤其是個挑戰。下面我們將一點一點往里挖。

?

首先我們來看一個簡單的使用泛型類的例子:

ArrayList<Integer>?aList?=?new?ArrayList<Integer>();

????aList.add(new?Integer(1));

????//?...

????Integer?myInteger?=?aList.get(0);

我們可以看到,在這個簡單的例子中,我們在定義aList的時候指明了它是一個直接受Integer類型的ArrayList,當我們調用aList.get(0)時,我們已經不再需要先顯式的將結果轉換成Integer,然后再賦值給myInteger了。而這一步在早先的Java版本中是必須的。也許你在想,在使用Collection時節約一些類型轉換就是Java泛型的全部嗎?遠不止。單就這個例子而言,泛型至少還有一個更大的好處,那就是使用了泛型的容器類變得更加健壯:早先,Collection接口的get()和Iterator接口的next()方法都只能返回Object類型的結果,我們可以把這個結果強制轉換成任何Object的子類,而不會有任何編譯期的錯誤,但這顯然很可能帶來嚴重的運行期錯誤,因為在代碼中確定從某個Collection中取出的是什么類型的對象完全是調用者自己說了算,而調用者也許并不清楚放進Collection的對象具體是什么類的;就算知道放進去的對象“應該”是什么類,也不能保證放到Collection的對象就一定是那個類的實例。現在有了泛型,只要我們定義的時候指明該Collection接受哪種類型的對象,編譯器可以幫我們避免類似的問題溜到產品中。我們在實際工作中其實已經看到了太多的ClassCastException,不是嗎?

?

泛型的使用從這個例子看也是相當易懂。我們在定義ArrayList時,通過類名后面的<>括號中的值指定這個ArrayList接受的對象類型。在編譯的時候,這個ArrayList會被處理成只接受該類或其子類的對象,于是任何試圖將其他類型的對象添加進來的語句都會被編譯器拒絕。

?

那么泛型是怎樣定義的呢?看看下面這一段示例代碼:(其中用E代替在實際中將會使用的類名,當然你也可以使用別的名稱,習慣上在這里使用大寫的E,表示Collection的元素。)

public?class?TestGenerics<E>?{

????????Collection<E>?col;

????????public?void?doSth(E?elem)?{

????????????col.add(elem);

????????????//?...

????????}

}

在泛型的使用中,有一個很容易有的誤解,那就是既然Integer是從Object派生出來的,那么ArrayList<Integer>當然就是ArrayList<Object>的子類。真的是這樣嗎?我們仔細想一想就會發現這樣做可能會帶來的問題:如果我們可以把ArrayList<Integer>向上轉型為ArrayList<Object>,那么在往這個轉了型以后的ArrayList中添加對象的時候,我們豈不是可以添加任何類型的對象(因為Object是所有對象的公共父類)?這顯然讓我們的ArrayList<Integer>失去了原本的目的。于是Java編譯器禁止我們這樣做。那既然是這樣,ArrayList<Integer>以及ArrayList<String>、ArrayList<Double>等等有沒有公共的父類呢?有,那就是ArrayList<?>。?在這里叫做通配符。我們為了縮小通配符所指代的范圍,通常也需要這樣寫: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具體是使用哪個類作為參數定義的,因此編譯器也無法讓這段代碼編譯通過。舉例來講,如果我們想往這個ArrayList中放一個SomeExtendedClass2的對象,我們如何保證它實際上不是其他的如ArrayList<SomeExtendedClass1>,而就是這個ArrayList<SomeExtendedClass2>呢?(還記得嗎?ArrayList<Integer>并非ArrayList<Object>的子類。)怎么辦?我們需要使用泛型方法。泛型方法的定義類似下面的例子:

public?static?<T?extends?SomeClass>?void?add?(Collection<T>?c,?T?elem)?{

????????c.add(elem);

}

其中T代表了我們這個方法期待的那個最終的具體的類,相關的聲明必須放在方法簽名中緊靠返回類型的位置之前。在本例中,它可以是SomeClass或者SomeClass的任何子類,其說明<T?entends?SomeClass>放在void關鍵字之前(只能放在這里)。這樣我們就可以讓編譯器確信當我們試圖添加一個元素到泛型的ArrayList實例中時,可以保證類型安全。

?

Java泛型的最大特點在于它是在語言級別實現的,區別于C#?2.0中的CLR級別。這樣的做法使得JRE可以不必做大的調整,缺點是無法支持一些運行時的類型甄別。一旦編譯,它就被寫死了,能提供的動態能力相當弱。

?

個人認為泛型是這次J2SE(TM)?5.0中引入的最重要的語言元素,給Java語言帶來的影響也是最大。舉個例子來講,我們可以看到,幾乎所有的Collections?API都被更新成支持泛型的版本。這樣做帶來的好處是顯而易見的,那就是減少代碼重復(不需要提供多個版本的某一個類或者接口以支持不同類的對象)以及增強代碼的健壯性(編譯期的類型安全檢查)。不過如何才能真正利用好這個特性,尤其是如何實現自己的泛型接口或類供他人使用,就并非那么顯而易見了。讓我們一起在使用中慢慢積累。

1.5.????????增強的for循環
你是否已經厭倦了每次寫for循環時都要寫上那些機械的代碼,尤其當你需要遍歷數組或者Collection,如:(假設在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語句的三個表達式順序弄錯;第二個表達式邏輯判斷不正確(漏掉一些、多出一些、甚至死循環);忘記移動游標;在循環體內不小心改變了游標的位置等等。為什么不能讓編譯器幫我們處理這些細節呢?在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);

????}

}

這樣的代碼顯得更加清晰和簡潔,不是嗎?具體的語法很簡單:使用":"分隔開,前面的部分寫明從數組或Collection中將要取出的類型,以及使用的臨時變量的名字,后面的部分寫上數組或者Collection的引用。加上泛型,我們甚至可以把第一個方法變得更加漂亮:

public?void?showAll?(Collection<String>?cs)?{

????for?(String?str?:?cs)?{

????????System.out.println(str);

????}

}

有沒有發現:當你需要將Collection<String>替換成String[],你所需要做的僅僅是簡單的把參數類型"Collection<String>"替換成"String[]",反過來也是一樣,你不完全需要改其他的東西。這在J2SE(TM)?5.0之前是無法想象的。

?

對于這個看上去相當方便的新語言元素,當你需要在循環體中訪問游標的時候,會顯得很別扭:比方說,當我們處理一個鏈表,需要更新其中某一個元素,或者刪除某個元素等等。這個時候,你無法在循環體內獲得你需要的游標信息,于是需要回退到原先的做法。不過,有了泛型和增強的for循環,我們在大多數情況下已經不用去操心那些煩人的for循環的表達式和嵌套了。畢竟,我們大部分時間都不會需要去了解游標的具體位置,我們只需要遍歷數組或Collection,對吧?

1.6.????????自動裝箱/自動拆箱
所謂裝箱,就是把值類型用它們相對應的引用類型包起來,使它們可以具有對象的特質,如我們可以把int型包裝成Integer類的對象,或者把double包裝成Double,等等。所謂拆箱,就是跟裝箱的方向相反,將Integer及Double這樣的引用類型的對象重新簡化為值類型的數據。

?

在J2SE(TM)?5.0發布之前,我們只能手工的處理裝箱和拆箱。也許你會問,為什么需要裝箱和拆箱?比方說當我們試圖將一個值類型的數據添加到一個Collection中時,就需要先把它裝箱,因為Collection的add()方法只接受對象;而當我們需要在稍后將這條數據取出來,而又希望使用它對應的值類型進行操作時,我們又需要將它拆箱成值類型的版本。現在,編譯器可以幫我們自動地完成這些必要的步驟。下面的代碼我提供兩個版本的裝箱和拆箱,一個版本使用手工的方式,另一個版本則把這些顯而易見的代碼交給編譯器去完成:

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中,我們不再需要顯式的去將一個值類型的數據轉換成相應的對象,從而把它作為對象傳給其他方法,也不必手工的將那個代表一個數值的對象拆箱為相應的值類型數據,只要你提供的信息足夠讓編譯器確信這些裝箱/拆箱后的類型在使用時是合法的:比方講,如果在上面的代碼中,如果我們使用的不是ArrayList<Integer>而是ArrayList或者其他不兼容的版本如ArrayList<java.util.Date>,會有編譯錯誤。

?

當然,你需要足夠重視的是:一方面,對于值類型和引用類型,在資源的占用上有相當大的區別;另一方面,裝箱和拆箱會帶來額外的開銷。在使用這一方便特性的同時,請不要忘記了背后隱藏的這些也許會影響性能的因素。

1.7.????????類型安全的枚舉
在介紹J2SE(TM)?5.0中引入的類型安全枚舉的用法之前,我想先簡單介紹一下這一話題的背景。

?

我們知道,在C中,我們可以定義枚舉類型來使用別名代替一個集合中的不同元素,通常是用于描述那些可以歸為一類,而又具備有限數量的類別或者概念,如月份、顏色、撲克牌、太陽系的行星、五大洲、四大洋、季節、學科、四則運算符,等等。它們通常看上去是這個樣子:

typedef?enum?{SPRING,?SUMMER,?AUTUMN,?WINTER}?season;

實質上,這些別名被處理成int常量,比如0代表SPRING,1代表SUMMER,以此類推。因為這些別名最終就是int,于是你可以對它們進行四則運算,這就造成了語意上的不明確。

?

Java一開始并沒有考慮引入枚舉的概念,也許是出于保持Java語言簡潔的考慮,但是使用Java的廣大開發者對于枚舉的需求并沒有因為Java本身沒有提供而消失,于是出現了一些常見的適用于Java的枚舉設計模式,如int?enum和typesafe?enum,還有不少開源的枚舉API和不開源的內部實現。

?

我大致說一下int?enum模式和typesafe?enum模式。所謂int?enum模式就是模仿C中對enum的實現,如:

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中的枚舉沒有太多本質上的區別,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");

}

后一種實現首先通過私有的構造方法阻止了對該類的繼承和顯式實例化,因而我們只可能取得定義好的四種Season類別,并且提供了方便的toString()方法獲取有意義的說明,而且由于這是一個完全意義上的類,所以我們可以很方便的加入自己的方法和邏輯來自定義我們的枚舉類。

?

最終,Java決定擁抱枚舉,在J2SE(TM)?5.0中,我們看到了這一變化,它所采用的設計思路基本上就是上面提到的typesafe?enum模式。它的語法很簡單,用一個實際的例子來說,要定義一個枚舉,我們可以這樣寫:

public?enum?Language?{CHINESE,?ENGLISH,?FRENCH,?HUNGARIAN}

接下來我們就可以通過Language.ENGLISH來使用了。呃…這個例子是不是有點太小兒科了,我們來看一個復雜點的例子。使用Java的類型安全枚舉,我們可以為所有枚舉元素定義公用的接口,然后具體到每個元素本身,可以針對這些接口實現一些特定的行為。這對于那些可以歸為一類,又希望能通過統一的接口訪問的不同操作,將會相當方便。通常,為了實現類似的功能,我們需要自己來維護一套繼承關系或者類似的枚舉模式。這里借用Java官方網站上的一個例子:

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);

}

在這個枚舉中,我們定義了四個元素,分別對應加減乘除四則運算,對于每一種運算,我們都可以調用eval()方法,而具體的方法實現各異。我們可以通過下面的代碼來試驗上面這個枚舉類:

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));

????}

}

怎么樣,使用枚舉,我們是不是能夠很方便的實現一些有趣的功能?其實說穿了,Java的類型安全枚舉就是包含了有限數量的已生成好的自身實例的一種類,這些現成的實例可以通過類的靜態字段來獲取。

1.8.????????可變長度參數
顧名思義,可變長度參數就是指在方法的參數體中,只要定義恰當,我們可以使用任意數量的參數,類似于使用數組。在J2SE(TM)?5.0中,一個新的語法被引入,就是在參數類型名稱后面加上"...",表示該方法可以接受多個該類型的參數。需要說明的是可變長度參數必須放在參數列表的最后,且一個方法只能包含一個這樣的參數。在方法體內部,這樣的參數被當作數組處理,看上去代碼應該類似這個樣子:

public?String?testVararg(String...?args)?{

????StringBuilder?sb?=?new?StringBuilder();

????for?(String?str?:?args)?{

????????sb.append(str);

????}

????return?sb.toString();

}

這樣的方法簽名跟你寫成testVararg(String[]?args)的區別在于:在調用時,你不再需要傳入一個包裝好的String數組,你只需要簡單的寫一連串String參數,以逗號隔開即可,就如同這個方法正好有一個重載的版本是接受那么多個String參數一樣。

1.9.????????靜態引入
所謂靜態引入就是指除了引入類之外,我們現在又多了一種選擇:引入某個類的靜態字段。如:

import?static?java.lang.Math.PI;

或者

import?static?java.lang.Math.*;

這樣我們在接下來的代碼中,當我們需要使用某個被引入的靜態字段時,就不用再寫上前面的類名了。當然,出現名字沖突時,跟原來的類引入一樣,還是需要前綴以示區分。我個人認為這個新語言元素意義不大。當引入太多靜態字段后,代碼會變得難以閱讀和維護。由于靜態字段的名字通常不如類名那么具有描述性,我認為原先在靜態字段前寫上類名才是更好的選擇。不過,畢竟每個人的喜好和需求不同,如果你覺得它對你有用,既然提供了,那么就用咯。

1.10.??元數據(注解)
注解是J2SE(TM)?5.0引入的重要語言元素,它所對應的JSR是JSR?175,我們先來看看JSR?175的文檔對注解的說明:

?

注解不會直接影響程序的語義,而開發和部署工具則可以讀取這些注解信息,并作相應處理,如生成額外的Java源代碼、XML文檔、或者其他將與包含注解的程序一起使用的物件。

?

在之前的J2SE版本中,我們已經使用到了一部分早期的注解元素,如@deprecated等。這些元素通常被用于產生HTML的Javadoc。在J2SE(TM)?5.0中,注解被正式引入,且推到了Java歷史上前所未有的高度。

?

現在,注解不僅僅被用來產生Javadoc,更重要的,注解使得代碼的編譯期檢查更加有效和方便,同時也增強了代碼的描述能力。有一些注解是隨著J2SE(TM)?5.0一起發布的,我們可以直接使用。除此之外,我們也可以很方便的實現自定義的注解。在此基礎上,很多以前我們只能靠反射機制來完成的功能也變得更加容易實現。

?

我們來看現成的有哪些有用的注解:

?

首先是@Override,這個注解被使用在方法上,表明這個方法是從其父類繼承下來的,這樣的寫法可以很方便的避免我們在重寫繼承下來的方法時,不至于不小心寫錯了方法簽名,且悄悄的溜過了編譯器,造成隱蔽性相當高的bug。

?

其次是@Deprecated,表明該項(類、字段、方法)不再被推薦使用。

?

還有一個@SuppressWarnings,表明該項(類、字段、方法)所涵蓋的范圍不需要顯示所有的警告信息。這個注解需要提供參數,如unchecked等等。

?

下面我通過一個例子向大家說明這些現成的注解的用法:

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.");

????}

}

當然,我們也完全可以寫自己的注解。注解定義的語法是@interface關鍵字。J2SE(TM)?5.0支持三種形式的注解:不帶參數的標記注解、帶一個參數的注解和帶多個參數的完整注解。下面分別舉例說明:

?

標記注解,類似@Deprecated,如:

@interface?SomeEmptyAnnotation?{}

單個參數的注解,如:

@interface?MySingleElementAnnotation?{

????String?value();

}

以及多個參數的注解,如:

@interface?MyAnnotationForMethods?{

????int?index();

????String?info();

????String?developer()?default?"Sean?GAO";

}

?

我們可以看到,注解的定義跟interface的定義相當類似,我們還可以指定默認值。對于這些注解,我們也可以為其添加注解,所謂“注解的注解”。比方講,我們通常會使用@Target指定注解的作用對象,以及用@Retention指定注解信息寫入的級別,如源代碼、類文件等等。舉個例子:

@Target(ElementType.METHOD)

@Retention(RetentionPolicy.SOURCE)

public?@interface?SignedMethod?{

}

在使用時,我們需要在注解名稱前面寫上@,然后()中指定參數值,如:

@MyAnnotationForMethods?(

????????index?=?1,?

????????info?=?"This?is?a?method?to?test?MyAnnotation.",

????????developer?=?"Somebody?else"

)

public?void?testMethod1()?{

????//?...

}

注解的最大作用在于它在源代碼的基礎上增加了有用的信息,使得源代碼的描述性更強。這些信息可以被代碼之外的工具識別,從而可以很方便的增加外部功能,以及減少不必要的相關代碼/文件的維護。這里我想簡單提一個超出J2SE(TM)?5.0范疇的話題:在未來的EJB?3.0規范中會有相當多的對注解的應用,讓我們預覽一下將來的無狀態會話bean用注解來定義會是什么樣子:

@Stateless?public?class?BookShelfManagerBean?{

????public?void?addBook(Book?aBook)?{

????????//?business?logic?goes?here...

????}

????public?Collection?getAllBooks()?{

????????//?business?logic?goes?here...

????}

????//?...

}

我們甚至不用寫任何接口和部署描述符,這些工作將完全由外部工具通過讀取注解加上反射來完成,這不是很好嗎?

1.11.??C風格格式化輸出
Java總算也有類似C的printf()風格的方法了,方法名同樣叫作printf(),這一特性依賴于前邊提到的可變長度參數。舉個例子來說,我們現在可以寫:

System.out.printf("%s?has?a?value?of?%d.%n",?someString,?a);

?

怎么樣,看上去還不錯吧?需要注意的是Java為了支持多平臺,新增了%n標示符,作為對\n的補充。有關Java格式化輸出的具體語法,請參考java.util.Formatter的API文檔。

1.12.??結語
在這一篇介紹性的文章中,我們一起領略了J2SE?5.0帶來的新的語言元素,不知道大家是否也跟筆者一樣,感受到了這些新特性在提高我們的開發效率上所作的巨大努力。其實不只是語言元素,J2SE(TM)?5.0的發布在其他很多方面都作了不小的改進,包括虛擬機、新的API類庫等等,性能和功能上都有大幅提升。

?

對于主要靠J2EE吃飯的朋友來講,也許真正意義上要在工作中充分利用這些新的元素,恐怕要等主流的J2EE服務器都支持J2EE(TM)?5.0的那一天了,對此我充滿期待。