大內高手專欄:

從編譯器與 VM 角度分析 Java2 v5.0 語言的新特色

作者:蔡學鏞

2004 年 10 月

不久前,Sun 將 Java2 v1.5 改名為 Java2 v5.0,已經推出正式版。而此時,.NET Framework 2.0 (1.2) 也進入 Beta 1。這兩個競爭的平台不只是在推出的時間點接近,就連改版本號碼以壯氣勢的動作也一致,還不僅如此,如果你仔細看看 Java 語言的新特色,你會發現,除了 C# 語言的少數特色 (例如 Property、Event、Delegate、Indexer…) Java 還不具備之外,Java 語言已經越改越像 C# 了。見賢思齊乎?

從 1997 年第一季的 Java 1.1 推出之後,長達八年的時間,Java Class File 格式完全是靜止的。這段時間,Java 語言也僅有一些微不足道的小變動,Sun 將全部的精力放在各種 Java API 的制訂上。隨著 .NET 的推出對 Java 造成威脅,Java 語言現在也終於睡醒了。我們可以看到 Java2 1.5 上,新增許多重要的語言特色,其中包括了:

  • Autoboxing/Unboxing
  • Generics
  • Metadata
  • Typesafe Enums
  • Varargs

本文章假設讀者已經對 Java 語言的這些新特色有初步的認識,我會從編譯器與 VM 的角度來簡略地分析這些語言特色,並比較 Java 與 .NET 兩大平台作法的異同。

認識 Java Class File 內的擴充機制

當 Java 1.0 推出時,Java Class File 格式 (.class) 就已經固定下來,一直到現在都沒有推翻此格式。當時為了因應未來的需求,Java Class File 設計了 Attribute 機制,可以在以後因應需求而加以擴充,算是一種有遠見的設計。

Attribute 是用來進行「補充說明」,例如:Field 可以具備「ConstantValue」 Attribute,用來指定此 Field 的初始值 (initial value)。Method 可以具備「Code」 Attribute,用來存放 Java Bytecode。

在 Java 1.1 時,為了支援 Java 語言的 Inner Class 語法和匿名的 Inner Class,Java Class File Format 新增加了「InnerClasses」 Attribute以及「Synthetic」 Attribute。此後,在 1.2 到 1.4 的時代,都沒有任何的擴充。

Java2在5.0 版中,一口氣新增了 8 個 Attribute,分別是:

  • 「EnclosingMethod」 Attribute:Anonymous Class 或 Local Inner Class 必須使用此 Attribute 來紀錄此 Class 的活動範圍 (Scope) 為何。
  • 「Signature」 Attribute:Generics 的 Class、Method、或 Filed 必須使用此 Attribute 來記錄其類型 (Type)。
  • 「LocalVariableTypeTable」 Attribute:主要是給 debugger 使用,目的類似「LocalVariableTable」 Attribute,只不過「LocalVariableTable」 Attribute 用來記錄區域變數名稱,但「LocalVariableTypeTable」 Attribute 用來記錄區域變數型態。
  • 「RuntimeVisibleAnnotations」 Attribute:留待稍後討論到 Metadata 時再介紹。
  • 「 RuntimeInvisibleAnnotations」 Attribute:留待稍後討論到 Metadata 時再介紹。
  • 「 RuntimeVisibleParameterAnnotations」 Attribute:留待稍後討論到 Metadata 時再介紹。
  • 「 RuntimeInvisibleParameterAnnotations」 Attribute:留待稍後討論到 Metadata 時再介紹。
  • 「AnnotationDefault」 Attribute:留待稍後討論到 Metadata 時再介紹。

這些新增的 Attribute,都是為了因應 Java 語言的新特色,特別是,其中有 5 個 Attribute 是為了因應 Metadata 而設計的。這 8 個新增的 Attribute,加上以前 (1.0 與 1.1 版) 定義的 9 個 Attribute,Java 5.0 共有 17 個 Attribute。

Autoboxing/Unboxing

.NET 在 1.0 就已經開始支援 Autoboxing/Unboxing,Java 終於也在 1.5 開始支援此功能了。但是,Java 對於 Autoboxing/Unboxing 的支援,僅在語言層級,並未深入 VM。換句話說,Autoboxing/Unboxing 的支援,完全是利用編譯器來達成的,在 Java Bytecode 中,並無 boxing/unboxing 相關的 opcode。

.NET 對於 Autoboxing/Unboxing 的支援,則是由 C# 編譯器和 VM 雙管齊下:編譯器負責「auto」的部分,VM 負責 boxing/unboxing 的部分。.NET CIL 的指令集 (Instruction Set) 中,具有 box (opcode=0x8C) 以及 unbox (opcode=0x79) 指令,正是進行 boxing/unboxing 之用的。因為 .NET 對於 box/unbox 的動作提供 VM 指令,所以在 box/unbox 的效率上可以比 Java 更好,在 Bytecode 的體積上也比 Java 更少。

剛剛提到,C# 編譯器負責 Auto 的部分,但事實上,C# 語言只支援 Auto-Boxing,不支援 Auto-Unboxing,這樣的設計頗為合理,許多 C# 的書對此有解釋 (建議閱讀 Tom Archer 著作的 Inside C#, 2nd Ed.),我不在此贅述。但是 Java 5.0 則同時支援 Auto-Boxing 和 Auto-Unboxing,Java 認為 Auto-Unboxing 比 Auto-Boxing 更有用。該不該支援 Auto-Unboxing,這是一個見仁見智的問題。

Generics

Java 在很早以前就打算支援 Generics,相關的規範 JSR-14 在這幾年如雷貫耳,但是在冗長的 JCP 過程中,JSR-14 足足拖了五年才在 5.0 中實現。但我們不以為苦,畢竟對於 Java 的程式員來說,這種苦守寒窯的漫長等待,倒也成了一種習慣。

早期的 JSR-14 (以及 JSR-14 的前身 GJ/Pizza) 完全只利用編譯器編出一堆 Bytecode 來達到 Generics,這麼做帶來一堆缺點,唯一的優點是不需要更動 JVM,可以完全相容於舊的 JVM。現在,Java2 v5.0 在 Class File Format 中,利用前述的 Attribute 機制,來實現 Generics。Java2 5.0 新增了一個名為「Signature」的 Attribute,可以用來標明哪些 Class、Field、與 Method 有用到 Generics。Java 的 Generics 機制是需要 JVM 參與的,但是參與程度不深,因為 JVM 指令集中並未有任何 Generics 相關的指令。

.NET 2.0 也開始支援 Generics,是由 VM 來支援,所以任何 .NET 平台上的語言都可以在未來輕易地享受到此特色。和 Java VM 相較,.NET CLR 2.0 對於 Generics 的支援更豐富,除了新增 Metadata,也新增 CIL 指令。

Metadata

原本 Java Class File Format 內就有 Metadata,除了 Java Bytecode (在「Code」 Attribute 內) 的部分之外,Java Class File 內的其他部分都可以算是 Metadata。只是 Java 的 Metadata 有下列的問題:

  • 採用樹狀結構,我認為這方面較 .NET 所採用的 Normalized Table (正規化表格) 結構差。
  • 提供的資訊量太少,遠比不上 .NET。

當我看到 Java2 1.5 準備支援 Metadata 時,我當時以為 Java 所謂的 Metadata 和 .NET 的 Metadata 是只同樣的概念,後來我才發現我錯了。Java 所謂的 Metadata 其實有一個更正確的名稱,叫做 Program Annotation Facility (程式註解工具),我傾向於使用 Annotation一詞,而不使用容易混淆的 Metadata 一詞。事實上,JVM 內部也是使用 Annotation 一詞來稱呼它。

Java 的 Annotation 其實就是 .NET 所謂的 Attribute。很明顯地,這又是一個 Java 師法於 .NET 的特色。.NET 的「CustomAttribute」 Metadata Table 用途等同於 Java 的 Annotation。.NET 的 Attribute 是一種 class,必須繼承自 System.Attribute。Java 的 Annotation 是一種 interface,必須繼承自 (擴充自) annotation.Annotation,且其 Class File 中,必需貼上 ACC_ANNOTATION 標籤,以為識別 (請看新版 JVM Spec 的 Table 4.1)。

出乎我意料之外的是,Java 竟然不使用 Class File Format 內既有的 Attribute 機制,而是為此對 Class File Format 作了相當大的擴充。前面所提到的八個新增的 Class File Attribute 中,有五個是和 Annotation 相關的 (比較奇特的是,Java 的 Annotation 還有分成可否被 Reflection API 查詢的兩種不同版本,但 .NET 的 Attribute 則一律可以被 Reflection API 查詢):

  • 「RuntimeVisibleAnnotations」 Attribute:紀錄可以被 Reflection API 查詢的 Annotation,註解的對象是 Class、Method、Field。
  • 「RuntimeInvisibleAnnotations」 Attribute:紀錄「不」可以被 Reflection API 查詢的 Annotation,註解的對象是 Class、Method、Field。
  • 「RuntimeVisibleParameterAnnotations」 Attribute:同「RuntimeVisibleAnnotations」 Attribute,但只能用於 Method,註解的對象是此 Method 的參數 (Parameter)。
  • 「RuntimeInvisibleParameterAnnotations」 Attribute:同「RuntimeInvisibleAnnotations」 Attribute,但只能用於 Method,註解的對象是此 Method 的參數。
  • 「AnnotationDefault」 Attribute:用於 Method,紀錄 Annotation 內 Element 的預設值 (default value)。

我不喜歡 Java 在 Class File 中把 Annotation 搞得這麼複雜。我認為 .NET 的「CustomAttribute」 Metadata Table 設計比較高明。

Enum 與 Varargs

對於 Java 2 v5.0 來說,如果某個 class 是 enum,在 class file 中會被貼上一個 ACC_ENUM 標籤,以為識別 (請看新版 JVM Spec 的 Table 4.1)。同理,如果某個 field 是 enum,也會被貼上 ACC_ENUM 標籤 (請看新版 JVM Spec 的 Table 4.5)。

在 .NET 中,所有的 enum 都必須直接繼承自 System.Enum (C# 提供 syntactic sugar,由編譯器自動幫我們建立繼承關係)。.NET 的 System.Enum 繼承自 System.ValueType,這意味著在 .NET中enum 是 copy-by-value 的 type,這樣的設計方式和 Java 完全不同,Java 的 enum 是 reference-type (亦即 copy-by-reference),這是相當大的差異。

Java 的 enum 是 singleton class (只能有一個 instance)。Java 透過編譯器和 JVM 聯手來防堵 enum 產生超過一個 class:不能利用 new、clone()、de-serialization、以及 Reflection API 來產生 enum 的 instance。

Java2 v5.0 開始支援 Varargs (不固定引數個數),C#/.NET 早就在 1.0 版時開始支援 Varargs,兩者的語法一樣,實作的方法也一樣:都是利用編譯器來幫助產生一個陣列 (array),然後將這些資料放進陣列中,再把陣列當參數傳進 method 中,製造出不固定引數個數的假象 (又是 Syntactic Sugar)。

新特色的加入增加語言的複雜度

我還記得在 Java 1.0 時,Sun 振振有詞地表明,Java 不支援 Nested Class,因為這會增加程式「複雜度」;言猶在耳,他們旋即在一年後推出的 Java 1.1 中開始支援 Nested Class,原因是:不支援 Nested Class 就不容易實作 JavaBeans 以及新的委任事件模型 (Delegation Event Model)。在 Java 1.0 時,Sun 也振振有詞地表明,Java 不支援 Template (即 Generics),因為這會增加程式「複雜度」;言猶在耳,後來 JSR-14 Generics 的出現,著實讓我錯愕一番。我還記得在 Java 1.0 時,Sun 振振有詞地表明,Java 不支援 enum,因為用 public static int field 就可以取代 enum,如果 Java 語言支援 enum,會增加程式的「複雜度」;言猶在耳,現在 Java 卻開始支援 enum。我還記得在 Java 1.0 時,Sun 振振有詞地表明,Java 不支援 Varargs,因為這會增加程式的「複雜度」;言猶在耳,現在 Java 也開始支援 Varargs。......現在 Java 同時支援 Nested Class、Template、Enum、Varargs,我根據 Sun 以前的說法來進行合理的推斷,Java 已經變成一種很複雜的語言 :p

Java 1.0 時,Sun 在 Java 的優點中列出許多項,其中一項是簡單 (Simple)。但是經過這段時間的演變,現在任何人都不該宣稱 Java 是一個簡單的語言,儘管 Java 語言仍然比 C++ 簡單許多,且比 C# 簡單一些。

意見與支援

 您有任何問題、意見或建議嗎?您可以透過下列電子郵件與作者連絡:
 xy.cai@msa.hinet.net

更多資訊

想知道大內高手專欄的其他文章嗎?請至此專欄所有列表