Java 5 添加了許多強大的語言特性:泛型、枚舉、注釋、自動裝箱和增強的 for 循環。但是,許多工作組仍然被綁定在 JDK 1.4 或以前的版本上,可能需要花些時間才能使用新版本。但是,這些開發人員仍然可以使用這些功能強大的語言特性,同時在 JVM 早期版本上部署。
隨著最新的 Java 6.0 的發布,您可能認為 Java 5 的語言特性是 “舊的新特性”。但是即使在現在,當我詢問開發人員在開發時使用的 Java 平臺的版本時,通常只有一半人在使用 Java 5 —— 另一半則只能表示羨慕。他們非常希望使用 Java 5 中添加的語言特性,例如泛型和注釋,但仍有許多因素妨礙他們這樣做。
不能利用 Java 5 特性的開發人員包括那些開發組件、庫或應用程序框架的開發人員。因為他們的客戶可能仍然在使用 JDK 1.4 或以前的版本,并且 JDK 1.4 或以前的 JVM 不能裝載用 Java 5 編譯的類,所以使用 Java 5 語言特性會把他們的客戶基數限制在已經遷移到 Java 5 的公司。
另一類經常避免使用 Java 5 的開發人員是使用 Java EE 的開發人員。許多開發團隊不愿在 Java EE 1.4 及以前的版本上使用 Java 5,因為擔心其應用服務器的廠商不支持 Java 5。這些開發人員要遷移到 Java EE 5 可能還有待時日。除了 Java EE 5 和 Java SE 5 規范之間的滯后,商業 Java EE 5 容器沒有必要在規范剛剛制定好就能使用,企業也沒有必要在應用服務器出現下一個版本時就立即升級,而且在升級應用服務器之后,可能還需要花些時間在新平臺上驗證其應用程序。
Java 5 語言特性的實現
Java 5 中添加的語言特性 —— 泛型、枚舉、注釋、自動裝箱和增強的 for 循環 —— 不需要修改 JVM 的指令集,幾乎全部是在靜態編譯器(javac)和類庫中實現的。當編譯器遇到使用泛型的情況時,會試圖檢查是否保證了類型安全(如果不能檢查,會發出 “unchecked cast”),然后發出字節碼,生成的字節碼與等價的非泛型代碼、類型強制轉換所生成的字節碼相同。類似的,自動裝箱和增強的 for 循環僅僅是等價的 “語法糖”,只是更復雜的語法和枚舉被編譯到普通的類中。
在理論上,可以采用 javac 生成的類文件,在早期的 JVM 中裝入它們,這實際上正是 JSR 14(負責泛型的 Java Community Process 工作組)的成立目的。但是,其他問題(例如注釋的保持)迫使類文件的版本在 Java 1.4 和 Java 5 之間變化,因此妨礙了早期 JVM 中裝入用 Java 5 編譯的代碼。而且,在 Java 5 中添加的有些語言特性依賴于 Java 5 庫。如果用 javac -target 1.5 編譯類,并試圖將它裝入早期 JVM 中,就會得到 UnsupportedClassVersionError,因為 -target 1.5 選項生成的類的類文件版本是 49,而 JDK 1.4 只支持版最大為 48 的類文件版本。
for-each 循環
增強的 for 循環有時叫做 for-each 循環,編譯器編譯它的時候,情形與程序員提供舊式 for 循環一樣。for-each 循環能夠迭代數組或集合中的元素。清單 1 顯示了用 for-each 在集合上迭代的語法:
清單 1. for-each 循環
Collection fooCollection = ...
for (Foo f : fooCollection) {
doSomething(f);
} |
編譯器把這個代碼轉換成等價的基于迭代器的循環,如清單 2 所示:
清單 2. 清單 1 基于迭代器的等價循環
for (Iterator iter=f.iterator(); f.hasNext();) {
Foo f = iter.next();
doSomething(f);
} |
編譯器如何知道提供的參數有一個 iterator() 方法呢? javac 編譯器的設計者可能已經內置了對集合框架的理解,但是這種方法有些不必要的限制。所以,創建了一個新的接口 java.lang.Iterable(請參閱清單 3 ),并翻新集合類使其實現 Iterable 接口。這樣,不是在核心集合框架上構建的容器類也能利用新的 for-each 循環。但是這樣做會形成對 Java 5 類庫的依賴,因為在 JDK 1.4 中沒有 Iterable。
清單 3. Iterable 接口
public interface Iterable {
Iterator iterator();
} |
枚舉和自動裝箱
正像 for-each 循環一樣,枚舉也要求來自類庫的支持。當編譯器遇到枚舉類型時,生成的類將擴展庫類 java.lang.Enum。但是,同 Iterable 一樣,在 JDK 1.4 類庫中也沒有 Enum 類。
類似的,自動裝箱依賴于添加到原始包裝器類(例如 Integer)的 valueOf() 方法。當裝箱需要從 int 轉換到 Integer 時,編譯器并不調用 new Integer(int),而是生成對 Integer.valueOf(int) 的調用。valueOf() 方法的實現利用 享元(flyweight)模式 為常用的整數值緩存 Integer 對象(Java 6 的實現緩存從 -128 到 127 的整數),由于消除了冗余的實例化,可能會提高性能。而且,就像 Iterable 和 Enum 一樣,valueOf() 方法在 JDK 1.4 類庫中也不存在。
變長參數
當編譯器遇到用變長參數列表定義的方法時,會把其轉換成包含正確組件類型數組的方法;當編譯器遇到帶有變長參數列表方法的調用時,就把參數裝進數組。
注釋
定義了注釋的之后,可以用 @Retention 對它進行注釋,它可以決定編譯器對使用這個注釋的類、方法或字段執行什么處理。已經定義的保持策略有 SOURCE (在編譯時舍棄注釋數據)、CLASS (在類文件中記錄注釋)或 RUNTIME (在類文件中記錄注釋,并在運行時保留注釋,這樣就可以反射地訪問它們了)。
其他的庫依賴關系
在 Java 5 之前,當編譯器遇到嘗試連接兩個字符串的情況時,會使用幫助器類 StringBuffer 執行連接。在 Java 5 及以后的版本中,轉而調用新的 StringBuilder 類,JDK 1.4 及以前的類庫中不存在該類。
訪問 Java 5 特性
因為語言特性對庫支持的依賴,即使使用 Java 5 編譯器生成的類文件能夠裝入早期 JVM 版本,執行也會因為類裝入錯誤而失敗。但是,通過對字節碼進行適當轉換,仍有可能解決這些問題,因為這些遺漏的類并不包含實際的新功能。
JSR 14
在 Java 泛型規范(以及其他 Java 5 新添加的語言特性)的開發期間,在 javac 編譯器中添加了試驗性的支持,以便讓它能使用 Java 5 的語言特性,并生成能在 Java 1.4 JVM 上運行的字節碼。雖然這些特性不受支持(甚至是文檔),但許多開源項目都使用了它們,使得開發人員能使用 Java 5 語言特性編碼,并生成能在早期 JVM 上使用的 JAR 文件。而且,既然 javac 是開源的,那么這個特性有可能得到第三方的支持。要激活這些特性,可以用 -source 1.5 和 -target jsr14 選項調用 javac。
javac 的 JSR 14 目標模式使編譯器生成與 Java 5 語言特性對應的 JDK 1.4 兼容字節碼:
- 泛型和變長參數:編譯器在泛型出現的地方插入的強制轉換不依賴類庫,所以能夠在 Java 5 之前的 JVM 上很好地執行。類似的,編譯器在出現變長參數列表的地方生成的代碼也不依賴類庫。
- for-each 循環:當迭代數組時,編譯器生成歸納變量和標準的數組迭代語法。當在 Collection 上迭代時,編譯器生成標準的基于迭代器的語法。當在非集合的 Iterable 上迭代時,編譯器生成錯誤。
- 自動裝箱:編譯器不生成對包裝器類的 valueOf() 方法的調用,而是生成對構造函數的調用。
- 字符串連接:javac 的 JSR 14 目標模式使編譯器生成對 StringBuffer 的調用而不是對 StringBuilder 的調用。
- 枚舉:javac JSR 14 目標模式對枚舉沒有特殊支持。嘗試使用枚舉的代碼會失敗,在尋找 java.lang.Enum 基類時出現 NoClassDefFoundError。
使用 JSR 14 目標模式允許在 “簡易” 情況下編寫使用泛型、自動裝箱和 for-each 循環的代碼,這對多數項目來說可能足夠了。這很方便,如果不支持的話,編譯器會一次生成基本兼容的字節碼。