Java 5 添加了許多強(qiáng)大的語(yǔ)言特性:泛型、枚舉、注釋、自動(dòng)裝箱和增強(qiáng)的 for 循環(huán)。但是,許多工作組仍然被綁定在 JDK 1.4 或以前的版本上,可能需要花些時(shí)間才能使用新版本。但是,這些開(kāi)發(fā)人員仍然可以使用這些功能強(qiáng)大的語(yǔ)言特性,同時(shí)在 JVM 早期版本上部署。

  隨著最新的 Java 6.0 的發(fā)布,您可能認(rèn)為 Java 5 的語(yǔ)言特性是 “舊的新特性”。但是即使在現(xiàn)在,當(dāng)我詢問(wèn)開(kāi)發(fā)人員在開(kāi)發(fā)時(shí)使用的 Java 平臺(tái)的版本時(shí),通常只有一半人在使用 Java 5 —— 另一半則只能表示羨慕。他們非常希望使用 Java 5 中添加的語(yǔ)言特性,例如泛型和注釋?zhuān)杂性S多因素妨礙他們這樣做。

  不能利用 Java 5 特性的開(kāi)發(fā)人員包括那些開(kāi)發(fā)組件、庫(kù)或應(yīng)用程序框架的開(kāi)發(fā)人員。因?yàn)樗麄兊目蛻艨赡苋匀辉谑褂?JDK 1.4 或以前的版本,并且 JDK 1.4 或以前的 JVM 不能裝載用 Java 5 編譯的類(lèi),所以使用 Java 5 語(yǔ)言特性會(huì)把他們的客戶基數(shù)限制在已經(jīng)遷移到 Java 5 的公司。

  另一類(lèi)經(jīng)常避免使用 Java 5 的開(kāi)發(fā)人員是使用 Java EE 的開(kāi)發(fā)人員。許多開(kāi)發(fā)團(tuán)隊(duì)不愿在 Java EE 1.4 及以前的版本上使用 Java 5,因?yàn)閾?dān)心其應(yīng)用服務(wù)器的廠商不支持 Java 5。這些開(kāi)發(fā)人員要遷移到 Java EE 5 可能還有待時(shí)日。除了 Java EE 5 和 Java SE 5 規(guī)范之間的滯后,商業(yè) Java EE 5 容器沒(méi)有必要在規(guī)范剛剛制定好就能使用,企業(yè)也沒(méi)有必要在應(yīng)用服務(wù)器出現(xiàn)下一個(gè)版本時(shí)就立即升級(jí),而且在升級(jí)應(yīng)用服務(wù)器之后,可能還需要花些時(shí)間在新平臺(tái)上驗(yàn)證其應(yīng)用程序。

  Java 5 語(yǔ)言特性的實(shí)現(xiàn)

  Java 5 中添加的語(yǔ)言特性 —— 泛型、枚舉、注釋、自動(dòng)裝箱和增強(qiáng)的 for 循環(huán) —— 不需要修改 JVM 的指令集,幾乎全部是在靜態(tài)編譯器(javac)和類(lèi)庫(kù)中實(shí)現(xiàn)的。當(dāng)編譯器遇到使用泛型的情況時(shí),會(huì)試圖檢查是否保證了類(lèi)型安全(如果不能檢查,會(huì)發(fā)出 “unchecked cast”),然后發(fā)出字節(jié)碼,生成的字節(jié)碼與等價(jià)的非泛型代碼、類(lèi)型強(qiáng)制轉(zhuǎn)換所生成的字節(jié)碼相同。類(lèi)似的,自動(dòng)裝箱和增強(qiáng)的 for 循環(huán)僅僅是等價(jià)的 “語(yǔ)法糖”,只是更復(fù)雜的語(yǔ)法和枚舉被編譯到普通的類(lèi)中。

  在理論上,可以采用 javac 生成的類(lèi)文件,在早期的 JVM 中裝入它們,這實(shí)際上正是 JSR 14(負(fù)責(zé)泛型的 Java Community Process 工作組)的成立目的。但是,其他問(wèn)題(例如注釋的保持)迫使類(lèi)文件的版本在 Java 1.4 和 Java 5 之間變化,因此妨礙了早期 JVM 中裝入用 Java 5 編譯的代碼。而且,在 Java 5 中添加的有些語(yǔ)言特性依賴(lài)于 Java 5 庫(kù)。如果用 javac -target 1.5 編譯類(lèi),并試圖將它裝入早期 JVM 中,就會(huì)得到 UnsupportedClassVersionError,因?yàn)?-target 1.5 選項(xiàng)生成的類(lèi)的類(lèi)文件版本是 49,而 JDK 1.4 只支持版最大為 48 的類(lèi)文件版本。

  for-each 循環(huán)

  增強(qiáng)的 for 循環(huán)有時(shí)叫做 for-each 循環(huán),編譯器編譯它的時(shí)候,情形與程序員提供舊式 for 循環(huán)一樣。for-each 循環(huán)能夠迭代數(shù)組或集合中的元素。清單 1 顯示了用 for-each 在集合上迭代的語(yǔ)法:

  清單 1. for-each 循環(huán)

  Collection fooCollection = ...
  for (Foo f : fooCollection) {
  doSomething(f);
  }

  編譯器把這個(gè)代碼轉(zhuǎn)換成等價(jià)的基于迭代器的循環(huán),如清單 2 所示:

  清單 2. 清單 1 基于迭代器的等價(jià)循環(huán)

  for (Iterator iter=f.iterator(); f.hasNext();) {
  Foo f = iter.next();
  doSomething(f);
  }

  編譯器如何知道提供的參數(shù)有一個(gè) iterator() 方法呢? javac 編譯器的設(shè)計(jì)者可能已經(jīng)內(nèi)置了對(duì)集合框架的理解,但是這種方法有些不必要的限制。所以,創(chuàng)建了一個(gè)新的接口 java.lang.Iterable(請(qǐng)參閱清單 3 ),并翻新集合類(lèi)使其實(shí)現(xiàn) Iterable 接口。這樣,不是在核心集合框架上構(gòu)建的容器類(lèi)也能利用新的 for-each 循環(huán)。但是這樣做會(huì)形成對(duì) Java 5 類(lèi)庫(kù)的依賴(lài),因?yàn)樵?JDK 1.4 中沒(méi)有 Iterable。

  清單 3. Iterable 接口

  public interface Iterable {
  Iterator iterator();
  }

枚舉和自動(dòng)裝箱

  正像 for-each 循環(huán)一樣,枚舉也要求來(lái)自類(lèi)庫(kù)的支持。當(dāng)編譯器遇到枚舉類(lèi)型時(shí),生成的類(lèi)將擴(kuò)展庫(kù)類(lèi) java.lang.Enum。但是,同 Iterable 一樣,在 JDK 1.4 類(lèi)庫(kù)中也沒(méi)有 Enum 類(lèi)。

  類(lèi)似的,自動(dòng)裝箱依賴(lài)于添加到原始包裝器類(lèi)(例如 Integer)的 valueOf() 方法。當(dāng)裝箱需要從 int 轉(zhuǎn)換到 Integer 時(shí),編譯器并不調(diào)用 new Integer(int),而是生成對(duì) Integer.valueOf(int) 的調(diào)用。valueOf() 方法的實(shí)現(xiàn)利用 享元(flyweight)模式 為常用的整數(shù)值緩存 Integer 對(duì)象(Java 6 的實(shí)現(xiàn)緩存從 -128 到 127 的整數(shù)),由于消除了冗余的實(shí)例化,可能會(huì)提高性能。而且,就像 Iterable 和 Enum 一樣,valueOf() 方法在 JDK 1.4 類(lèi)庫(kù)中也不存在。

  變長(zhǎng)參數(shù)

  當(dāng)編譯器遇到用變長(zhǎng)參數(shù)列表定義的方法時(shí),會(huì)把其轉(zhuǎn)換成包含正確組件類(lèi)型數(shù)組的方法;當(dāng)編譯器遇到帶有變長(zhǎng)參數(shù)列表方法的調(diào)用時(shí),就把參數(shù)裝進(jìn)數(shù)組。

  注釋

  定義了注釋的之后,可以用 @Retention 對(duì)它進(jìn)行注釋?zhuān)梢詻Q定編譯器對(duì)使用這個(gè)注釋的類(lèi)、方法或字段執(zhí)行什么處理。已經(jīng)定義的保持策略有 SOURCE (在編譯時(shí)舍棄注釋數(shù)據(jù))、CLASS (在類(lèi)文件中記錄注釋)或 RUNTIME (在類(lèi)文件中記錄注釋?zhuān)⒃谶\(yùn)行時(shí)保留注釋?zhuān)@樣就可以反射地訪問(wèn)它們了)。

  其他的庫(kù)依賴(lài)關(guān)系

  在 Java 5 之前,當(dāng)編譯器遇到嘗試連接兩個(gè)字符串的情況時(shí),會(huì)使用幫助器類(lèi) StringBuffer 執(zhí)行連接。在 Java 5 及以后的版本中,轉(zhuǎn)而調(diào)用新的 StringBuilder 類(lèi),JDK 1.4 及以前的類(lèi)庫(kù)中不存在該類(lèi)。

  訪問(wèn) Java 5 特性

  因?yàn)檎Z(yǔ)言特性對(duì)庫(kù)支持的依賴(lài),即使使用 Java 5 編譯器生成的類(lèi)文件能夠裝入早期 JVM 版本,執(zhí)行也會(huì)因?yàn)轭?lèi)裝入錯(cuò)誤而失敗。但是,通過(guò)對(duì)字節(jié)碼進(jìn)行適當(dāng)轉(zhuǎn)換,仍有可能解決這些問(wèn)題,因?yàn)檫@些遺漏的類(lèi)并不包含實(shí)際的新功能。

  JSR 14

  在 Java 泛型規(guī)范(以及其他 Java 5 新添加的語(yǔ)言特性)的開(kāi)發(fā)期間,在 javac 編譯器中添加了試驗(yàn)性的支持,以便讓它能使用 Java 5 的語(yǔ)言特性,并生成能在 Java 1.4 JVM 上運(yùn)行的字節(jié)碼。雖然這些特性不受支持(甚至是文檔),但許多開(kāi)源項(xiàng)目都使用了它們,使得開(kāi)發(fā)人員能使用 Java 5 語(yǔ)言特性編碼,并生成能在早期 JVM 上使用的 JAR 文件。而且,既然 javac 是開(kāi)源的,那么這個(gè)特性有可能得到第三方的支持。要激活這些特性,可以用 -source 1.5 和 -target jsr14 選項(xiàng)調(diào)用 javac。

  javac 的 JSR 14 目標(biāo)模式使編譯器生成與 Java 5 語(yǔ)言特性對(duì)應(yīng)的 JDK 1.4 兼容字節(jié)碼:

  •   泛型和變長(zhǎng)參數(shù):編譯器在泛型出現(xiàn)的地方插入的強(qiáng)制轉(zhuǎn)換不依賴(lài)類(lèi)庫(kù),所以能夠在 Java 5 之前的 JVM 上很好地執(zhí)行。類(lèi)似的,編譯器在出現(xiàn)變長(zhǎng)參數(shù)列表的地方生成的代碼也不依賴(lài)類(lèi)庫(kù)。
  •   for-each 循環(huán):當(dāng)?shù)鷶?shù)組時(shí),編譯器生成歸納變量和標(biāo)準(zhǔn)的數(shù)組迭代語(yǔ)法。當(dāng)在 Collection 上迭代時(shí),編譯器生成標(biāo)準(zhǔn)的基于迭代器的語(yǔ)法。當(dāng)在非集合的 Iterable 上迭代時(shí),編譯器生成錯(cuò)誤。
  •   自動(dòng)裝箱:編譯器不生成對(duì)包裝器類(lèi)的 valueOf() 方法的調(diào)用,而是生成對(duì)構(gòu)造函數(shù)的調(diào)用。
  •   字符串連接:javac 的 JSR 14 目標(biāo)模式使編譯器生成對(duì) StringBuffer 的調(diào)用而不是對(duì) StringBuilder 的調(diào)用。
  •   枚舉:javac JSR 14 目標(biāo)模式對(duì)枚舉沒(méi)有特殊支持。嘗試使用枚舉的代碼會(huì)失敗,在尋找 java.lang.Enum 基類(lèi)時(shí)出現(xiàn) NoClassDefFoundError。

  使用 JSR 14 目標(biāo)模式允許在 “簡(jiǎn)易” 情況下編寫(xiě)使用泛型、自動(dòng)裝箱和 for-each 循環(huán)的代碼,這對(duì)多數(shù)項(xiàng)目來(lái)說(shuō)可能足夠了。這很方便,如果不支持的話,編譯器會(huì)一次生成基本兼容的字節(jié)碼。