概述
J2SE(TM) 5.0引入了很多激進(jìn)的語(yǔ)言元素變化,這些變化或多或少減輕了我們開(kāi)發(fā)人員的一些編碼負(fù)擔(dān),其中的大部分也必然會(huì)被應(yīng)用到即將發(fā)布的J2EE(TM) 5.0中。主要的新特性包括:

  · 泛型

  · 增強(qiáng)的for循環(huán)

  · 自動(dòng)裝箱和自動(dòng)拆箱

  · 類(lèi)型安全的枚舉

  · 可變長(zhǎng)度參數(shù)

  · 靜態(tài)引入

  · 元數(shù)據(jù)(注解)

  · C風(fēng)格的格式化輸出

  這當(dāng)中,泛型、枚舉和注解可能會(huì)占用較大的篇幅,而其余的因?yàn)橛梅ㄖ苯亓水?dāng),抑或相對(duì)簡(jiǎn)單,我就稍作介紹,剩下的留給讀者去思考、去探索了。

  1.4. 泛型

  泛型這個(gè)題目相當(dāng)大,大到完全可以就這個(gè)話題寫(xiě)一本書(shū)。有關(guān)Java是否需要泛型和如何實(shí)現(xiàn)泛型的討論也早就在Java社群廣為流傳。終于,我們?cè)贘2SE(TM) 5.0中看到了它。也許目前Java對(duì)泛型的支持還算不上足夠理想,但這一特性的添加也經(jīng)足以讓我們欣喜一陣了。

  在接下來(lái)的介紹中,我們會(huì)了解到:Java的泛型雖然跟C++的泛型看上去十分相似,但其實(shí)有著相當(dāng)大的區(qū)別,有些細(xì)節(jié)的東西也相當(dāng)復(fù)雜(至少很多地方會(huì)跟我們的直覺(jué)背道而馳)。可以這樣說(shuō),泛型的引入在很大程度上增加了Java語(yǔ)言的復(fù)雜度,對(duì)初學(xué)者尤其是個(gè)挑戰(zhàn)。下面我們將一點(diǎn)一點(diǎn)往里挖。

  首先我們來(lái)看一個(gè)簡(jiǎn)單的使用泛型類(lèi)的例子:

    ArrayList<Integer> aList = new ArrayList<Integer>();
    aList.add(new Integer(1));
    // ...
    Integer myInteger = aList.get(0);

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

  泛型的使用從這個(gè)例子看也是相當(dāng)易懂。我們?cè)诙xArrayList時(shí),通過(guò)類(lèi)名后面的<>括號(hào)中的值指定這個(gè)ArrayList接受的對(duì)象類(lèi)型。在編譯的時(shí)候,這個(gè)ArrayList會(huì)被處理成只接受該類(lèi)或其子類(lèi)的對(duì)象,于是任何試圖將其他類(lèi)型的對(duì)象添加進(jìn)來(lái)的語(yǔ)句都會(huì)被編譯器拒絕。

  那么泛型是怎樣定義的呢?看看下面這一段示例代碼:(其中用E代替在實(shí)際中將會(huì)使用的類(lèi)名,當(dāng)然你也可以使用別的名稱(chēng),習(xí)慣上在這里使用大寫(xiě)的E,表示Collection的元素。)

    public class TestGenerics<E> {
    Collection<E> col;
    public void doSth(E elem) {
    col.add(elem);
    // ...
    }
   
       在泛型的使用中,有一個(gè)很容易有的誤解,那就是既然Integer是從Object派生出來(lái)的,那么ArrayList<Integer>當(dāng)然就是ArrayList<Object>的子類(lèi)。真的是這樣嗎?我們仔細(xì)想一想就會(huì)發(fā)現(xiàn)這樣做可能會(huì)帶來(lái)的問(wèn)題:如果我們可以把ArrayList<Integer>向上轉(zhuǎn)型為ArrayList<Object>,那么在往這個(gè)轉(zhuǎn)了型以后的ArrayList中添加對(duì)象的時(shí)候,我們豈不是可以添加任何類(lèi)型的對(duì)象(因?yàn)镺bject是所有對(duì)象的公共父類(lèi))?這顯然讓我們的ArrayList<Integer>失去了原本的目的。于是Java編譯器禁止我們這樣做。那既然是這樣,ArrayList<Integer>以及ArrayList<String>、ArrayList<Double>等等有沒(méi)有公共的父類(lèi)呢?有,那就是ArrayList<?>。?在這里叫做通配符。我們?yōu)榱丝s小通配符所指代的范圍,通常也需要這樣寫(xiě):ArrayList<? extends SomeClass>,這樣寫(xiě)的含義是定義這樣一個(gè)類(lèi)ArrayList,比方說(shuō)SomeClass有SomeExtendedClass1和SomeExtendedClass2這兩個(gè)子類(lèi),那么ArrayList<? extends SomeClass>就是如下幾個(gè)類(lèi)的父類(lèi):ArrayList<SomeClass>、ArrayList<SomeExtendedClass1>和ArrayList<SomeExtendedClass2>。 

       接下來(lái)我們更進(jìn)一步:既然ArrayList<? extends SomeClass>是一個(gè)通配的公用父類(lèi),那么我們可不可以往聲明為ArrayList<? extends SomeClass>的ArrayList實(shí)例中添加一個(gè)SomeExtendedClass1的對(duì)象呢?答案是不能。甚至你不能添加任何對(duì)象。為什么?因?yàn)锳rrayList<? extends SomeClass>實(shí)際上代表了所有ArrayList<SomeClass>、ArrayList<SomeExtendedClass1>和ArrayList<SomeExtendedClass2>三種ArrayList,甚至包括未知的接受SomeClass其他子類(lèi)對(duì)象的ArrayList。我們拿到一個(gè)定義為ArrayList<? extends SomeClass>的ArrayList的時(shí)候,我們并不能確定這個(gè)ArrayList具體是使用哪個(gè)類(lèi)作為參數(shù)定義的,因此編譯器也無(wú)法讓這段代碼編譯通過(guò)。舉例來(lái)講,如果我們想往這個(gè)ArrayList中放一個(gè)SomeExtendedClass2的對(duì)象,我們?nèi)绾伪WC它實(shí)際上不是其他的如ArrayList<SomeExtendedClass1>,而就是這個(gè)ArrayList<SomeExtendedClass2>呢?(還記得嗎?ArrayList<Integer>并非ArrayList<Object>的子類(lèi)。)怎么辦?我們需要使用泛型方法。泛型方法的定義類(lèi)似下面的例子:
    public static <T extends SomeClass> void add (Collection<T> c, T elem) {
    c.add(elem);
    }

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

  Java泛型的最大特點(diǎn)在于它是在語(yǔ)言級(jí)別實(shí)現(xiàn)的,區(qū)別于C# 2.0中的CLR級(jí)別。這樣的做法使得JRE可以不必做大的調(diào)整,缺點(diǎn)是無(wú)法支持一些運(yùn)行時(shí)的類(lèi)型甄別。一旦編譯,它就被寫(xiě)死了,能提供的動(dòng)態(tài)能力相當(dāng)弱。

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

  1.5. 增強(qiáng)的for循環(huán)

  你是否已經(jīng)厭倦了每次寫(xiě)for循環(huán)時(shí)都要寫(xiě)上那些機(jī)械的代碼,尤其當(dāng)你需要遍歷數(shù)組或者Collection,如:(假設(shè)在Collection中儲(chǔ)存的對(duì)象是String類(lèi)型的)

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

  這樣的代碼不僅顯得臃腫,而且容易出錯(cuò),我想我們大家在剛開(kāi)始接觸編程時(shí),尤其是C/C++和Java,可能多少都犯過(guò)以下類(lèi)似錯(cuò)誤的一種或幾種:把for語(yǔ)句的三個(gè)表達(dá)式順序弄錯(cuò);第二個(gè)表達(dá)式邏輯判斷不正確(漏掉一些、多出一些、甚至死循環(huán));忘記移動(dòng)游標(biāo);在循環(huán)體內(nèi)不小心改變了游標(biāo)的位置等等。為什么不能讓編譯器幫我們處理這些細(xì)節(jié)呢?在5.0中,我們可以這樣寫(xiě):

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

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

    public void showAll (Collection<String> cs) {
    for (String str : cs) {
    System.out.println(str);
    }
    }

  有沒(méi)有發(fā)現(xiàn):當(dāng)你需要將Collection替換成String[],你所需要做的僅僅是簡(jiǎn)單的把參數(shù)類(lèi)型"Collection"替換成"String[]",反過(guò)來(lái)也是一樣,你不完全需要改其他的東西。這在J2SE(TM) 5.0之前是無(wú)法想象的。

  對(duì)于這個(gè)看上去相當(dāng)方便的新語(yǔ)言元素,當(dāng)你需要在循環(huán)體中訪問(wèn)游標(biāo)的時(shí)候,會(huì)顯得很別扭:比方說(shuō),當(dāng)我們處理一個(gè)鏈表,需要更新其中某一個(gè)元素,或者刪除某個(gè)元素等等。這個(gè)時(shí)候,你無(wú)法在循環(huán)體內(nèi)獲得你需要的游標(biāo)信息,于是需要回退到原先的做法。不過(guò),有了泛型和增強(qiáng)的for循環(huán),我們?cè)诖蠖鄶?shù)情況下已經(jīng)不用去操心那些煩人的for循環(huán)的表達(dá)式和嵌套了。畢竟,我們大部分時(shí)間都不會(huì)需要去了解游標(biāo)的具體位置,我們只需要遍歷數(shù)組或Collection,對(duì)吧?

  1.6. 自動(dòng)裝箱/自動(dòng)拆箱

  所謂裝箱,就是把值類(lèi)型用它們相對(duì)應(yīng)的引用類(lèi)型包起來(lái),使它們可以具有對(duì)象的特質(zhì),如我們可以把int型包裝成Integer類(lèi)的對(duì)象,或者把double包裝成Double,等等。所謂拆箱,就是跟裝箱的方向相反,將Integer及Double這樣的引用類(lèi)型的對(duì)象重新簡(jiǎn)化為值類(lèi)型的數(shù)據(jù)。

  在J2SE(TM) 5.0發(fā)布之前,我們只能手工的處理裝箱和拆箱。也許你會(huì)問(wèn),為什么需要裝箱和拆箱?比方說(shuō)當(dāng)我們?cè)噲D將一個(gè)值類(lèi)型的數(shù)據(jù)添加到一個(gè)Collection中時(shí),就需要先把它裝箱,因?yàn)镃ollection的add()方法只接受對(duì)象;而當(dāng)我們需要在稍后將這條數(shù)據(jù)取出來(lái),而又希望使用它對(duì)應(yīng)的值類(lèi)型進(jìn)行操作時(shí),我們又需要將它拆箱成值類(lèi)型的版本。現(xiàn)在,編譯器可以幫我們自動(dòng)地完成這些必要的步驟。下面的代碼我提供兩個(gè)版本的裝箱和拆箱,一個(gè)版本使用手工的方式,另一個(gè)版本則把這些顯而易見(jià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中,我們不再需要顯式的去將一個(gè)值類(lèi)型的數(shù)據(jù)轉(zhuǎn)換成相應(yīng)的對(duì)象,從而把它作為對(duì)象傳給其他方法,也不必手工的將那個(gè)代表一個(gè)數(shù)值的對(duì)象拆箱為相應(yīng)的值類(lèi)型數(shù)據(jù),只要你提供的信息足夠讓編譯器確信這些裝箱/拆箱后的類(lèi)型在使用時(shí)是合法的:比方講,如果在上面的代碼中,如果我們使用的不是ArrayList而是ArrayList或者其他不兼容的版本如ArrayList,會(huì)有編譯錯(cuò)誤。

  當(dāng)然,你需要足夠重視的是:一方面,對(duì)于值類(lèi)型和引用類(lèi)型,在資源的占用上有相當(dāng)大的區(qū)別;另一方面,裝箱和拆箱會(huì)帶來(lái)額外的開(kāi)銷(xiāo)。在使用這一方便特性的同時(shí),請(qǐng)不要忘記了背后隱藏的這些也許會(huì)影響性能的因素。

  1.7. 類(lèi)型安全的枚舉

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

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

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

  實(shí)質(zhì)上,這些別名被處理成int常量,比如0代表SPRING,1代表SUMMER,以此類(lèi)推。因?yàn)檫@些別名最終就是int,于是你可以對(duì)它們進(jìn)行四則運(yùn)算,這就造成了語(yǔ)意上的不明確。

  Java一開(kāi)始并沒(méi)有考慮引入枚舉的概念,也許是出于保持Java語(yǔ)言簡(jiǎn)潔的考慮,但是使用Java的廣大開(kāi)發(fā)者對(duì)于枚舉的需求并沒(méi)有因?yàn)镴ava本身沒(méi)有提供而消失,于是出現(xiàn)了一些常見(jiàn)的適用于Java的枚舉設(shè)計(jì)模式,如int enum和typesafe enum,還有不少開(kāi)源的枚舉API和不開(kāi)源的內(nèi)部實(shí)現(xiàn)。

  我大致說(shuō)一下int enum模式和typesafe enum模式。所謂int enum模式就是模仿C中對(duì)enum的實(shí)現(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中的枚舉沒(méi)有太多本質(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");
    }

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

  最終,Java決定擁抱枚舉,在J2SE(TM) 5.0中,我們看到了這一變化,它所采用的設(shè)計(jì)思路基本上就是上面提到的typesafe enum模式。它的語(yǔ)法很簡(jiǎn)單,用一個(gè)實(shí)際的例子來(lái)說(shuō),要定義一個(gè)枚舉,我們可以這樣寫(xiě):

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

  接下來(lái)我們就可以通過(guò)Language.ENGLISH來(lái)使用了。呃…這個(gè)例子是不是有點(diǎn)太小兒科了,我們來(lái)看一個(gè)復(fù)雜點(diǎn)的例子。使用Java的類(lèi)型安全枚舉,我們可以為所有枚舉元素定義公用的接口,然后具體到每個(gè)元素本身,可以針對(duì)這些接口實(shí)現(xiàn)一些特定的行為。這對(duì)于那些可以歸為一類(lèi),又希望能通過(guò)統(tǒng)一的接口訪問(wèn)的不同操作,將會(huì)相當(dāng)方便。通常,為了實(shí)現(xiàn)類(lèi)似的功能,我們需要自己來(lái)維護(hù)一套繼承關(guān)系或者類(lèi)似的枚舉模式。這里借用Java官方網(wǎng)站上的一個(gè)例子:

    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);
    }
    在這個(gè)枚舉中,我們定義了四個(gè)元素,分別對(duì)應(yīng)加減乘除四則運(yùn)算,對(duì)于每一種運(yùn)算,我們都可以調(diào)用eval()方法,而具體的方法實(shí)現(xiàn)各異。我們可以通過(guò)下面的代碼來(lái)試驗(yàn)上面這個(gè)枚舉類(lèi):
    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));
    }
    }

  怎么樣,使用枚舉,我們是不是能夠很方便的實(shí)現(xiàn)一些有趣的功能?其實(shí)說(shuō)穿了,Java的類(lèi)型安全枚舉就是包含了有限數(shù)量的已生成好的自身實(shí)例的一種類(lèi),這些現(xiàn)成的實(shí)例可以通過(guò)類(lèi)的靜態(tài)字段來(lái)獲取。

  1.8. 可變長(zhǎng)度參數(shù)

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

    public String testVararg(String... args) {
    StringBuilder sb = new StringBuilder();
    for (String str : args) {
    sb.append(str);
    }
    return sb.toString();
    }

  這樣的方法簽名跟你寫(xiě)成testVararg(String[] args)的區(qū)別在于:在調(diào)用時(shí),你不再需要傳入一個(gè)包裝好的String數(shù)組,你只需要簡(jiǎn)單的寫(xiě)一連串String參數(shù),以逗號(hào)隔開(kāi)即可,就如同這個(gè)方法正好有一個(gè)重載的版本是接受那么多個(gè)String參數(shù)一樣。

  1.9. 靜態(tài)引入

  所謂靜態(tài)引入就是指除了引入類(lèi)之外,我們現(xiàn)在又多了一種選擇:引入某個(gè)類(lèi)的靜態(tài)字段。如:

  import static java.lang.Math.PI;

  或者

  import static java.lang.Math.*;

  這樣我們?cè)诮酉聛?lái)的代碼中,當(dāng)我們需要使用某個(gè)被引入的靜態(tài)字段時(shí),就不用再寫(xiě)上前面的類(lèi)名了。當(dāng)然,出現(xiàn)名字沖突時(shí),跟原來(lái)的類(lèi)引入一樣,還是需要前綴以示區(qū)分。我個(gè)人認(rèn)為這個(gè)新語(yǔ)言元素意義不大。當(dāng)引入太多靜態(tài)字段后,代碼會(huì)變得難以閱讀和維護(hù)。由于靜態(tài)字段的名字通常不如類(lèi)名那么具有描述性,我認(rèn)為原先在靜態(tài)字段前寫(xiě)上類(lèi)名才是更好的選擇。不過(guò),畢竟每個(gè)人的喜好和需求不同,如果你覺(jué)得它對(duì)你有用,既然提供了,那么就用咯。

  1.10. 元數(shù)據(jù)(注解)

  注解是J2SE(TM) 5.0引入的重要語(yǔ)言元素,它所對(duì)應(yīng)的JSR是JSR 175,我們先來(lái)看看JSR 175的文檔對(duì)注解的說(shuō)明:

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

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

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

  我們來(lái)看現(xiàn)成的有哪些有用的注解:

  首先是@Override,這個(gè)注解被使用在方法上,表明這個(gè)方法是從其父類(lèi)繼承下來(lái)的,這樣的寫(xiě)法可以很方便的避免我們?cè)谥貙?xiě)繼承下來(lái)的方法時(shí),不至于不小心寫(xiě)錯(cuò)了方法簽名,且悄悄的溜過(guò)了編譯器,造成隱蔽性相當(dāng)高的bug。

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

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

  下面我通過(guò)一個(gè)例子向大家說(shuō)明這些現(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)然,我們也完全可以寫(xiě)自己的注解。注解定義的語(yǔ)法是@interface關(guān)鍵字。J2SE(TM) 5.0支持三種形式的注解:不帶參數(shù)的標(biāo)記注解、帶一個(gè)參數(shù)的注解和帶多個(gè)參數(shù)的完整注解。下面分別舉例說(shuō)明:

    標(biāo)記注解,類(lèi)似@Deprecated,如:
    @interface SomeEmptyAnnotation {}
    單個(gè)參數(shù)的注解,如:
    @interface MySingleElementAnnotation {
    String value();
    }
    以及多個(gè)參數(shù)的注解,如:
    @interface MyAnnotationForMethods {
    int index();
    String info();
    String developer() default "Sean GAO";
    }

  我們可以看到,注解的定義跟interface的定義相當(dāng)類(lèi)似,我們還可以指定默認(rèn)值。對(duì)于這些注解,我們也可以為其添加注解,所謂“注解的注解”。比方講,我們通常會(huì)使用@Target指定注解的作用對(duì)象,以及用@Retention指定注解信息寫(xiě)入的級(jí)別,如源代碼、類(lèi)文件等等。舉個(gè)例子:

    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.SOURCE)
    public @interface SignedMethod {
    }
    在使用時(shí),我們需要在注解名稱(chēng)前面寫(xiě)上@,然后()中指定參數(shù)值,如:
    @MyAnnotationForMethods (
    index = 1,
    info = "This is a method to test MyAnnotation.",
    developer = "Somebody else"
    )
    public void testMethod1() {
    // ...
    }

  注解的最大作用在于它在源代碼的基礎(chǔ)上增加了有用的信息,使得源代碼的描述性更強(qiáng)。這些信息可以被代碼之外的工具識(shí)別,從而可以很方便的增加外部功能,以及減少不必要的相關(guān)代碼/文件的維護(hù)。這里我想簡(jiǎn)單提一個(gè)超出J2SE(TM) 5.0范疇的話題:在未來(lái)的EJB 3.0規(guī)范中會(huì)有相當(dāng)多的對(duì)注解的應(yīng)用,讓我們預(yù)覽一下將來(lái)的無(wú)狀態(tài)會(huì)話bean用注解來(lái)定義會(huì)是什么樣子:

    @Stateless public class BookShelfManagerBean {
    public void addBook(Book aBook) {
    // business logic goes here...
    }
    public Collection getAllBooks() {
    // business logic goes here...
    }
    // ...
    }

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

  1.11. C風(fēng)格格式化輸出

  Java總算也有類(lèi)似C的printf()風(fēng)格的方法了,方法名同樣叫作printf(),這一特性依賴(lài)于前邊提到的可變長(zhǎng)度參數(shù)。舉個(gè)例子來(lái)說(shuō),我們現(xiàn)在可以寫(xiě):

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

  怎么樣,看上去還不錯(cuò)吧?需要注意的是Java為了支持多平臺(tái),新增了%n標(biāo)示符,作為對(duì)\n的補(bǔ)充。有關(guān)Java格式化輸出的具體語(yǔ)法,請(qǐng)參考java.util.Formatter的API文檔。

  1.12. 結(jié)語(yǔ)

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

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