讀古今文學網 > Java程序員修煉之道 > 7.5 JVM對備選語言的支持 >

7.5 JVM對備選語言的支持

一種語言要在JVM上運行可能有兩種方式:

  • 有一個產生類文件的編譯器;
  • 有一個用JVM字節碼實現的解釋器。

無論哪種方式,通常都會有個運行時環境為執行該語言編寫的程序提供支持。圖7-4展示了Java和一種典型非Java語言的運行時環境棧。

圖7-4 非Java語言運行時支持

這些運行時支持系統複雜度各不相同,主要取決於給定的非Java語言運行時所要掌握的資源數量。幾乎在所有情況下,運行時都會作為可執行程序類路徑(classpath)上的一組JAR文件,並且要在程序執行開始之前啟動。

本書的重點是編譯型語言。至於Rhino等解釋型語言,只是為了內容的完整性才提一下,所以我們不會在上面花太多篇幅。在本節剩下的內容中,我們會介紹備選語言所需的運行時支持(甚至還包括編譯型語言),然後探討編譯器小說(compiler fiction)——那些可能不會出現在底層字節碼中、由編譯器合成的該語言特有的功能。

7.5.1 非Java語言的運行時環境

有一種評估語言運行時環境複雜度的簡單辦法:看運行時實現中JAR文件的大小。按這個標準,Clojure的運行時環境量級很輕,而JRuby語言則需要更多支持。

這個標準其實不是特別公平,因為有些語言把很多類庫和功能都打包在標準發佈版裡了,而有些卻不這麼做。但如果不細究的話,它是個挺有用的經驗。

通常來說,運行時環境是要幫助非Java語言的類型系統和其他特性符合期望的語義。備選語言對基本編程概念的看法有時和Java並不完全一致。

比如,Java的面向對像方式並不是放之四海而皆准的。在Ruby中,運行時可以往一個單獨的對象裡添加一個額外的方法,而這個方法在定義類時還不知道是什麼,也不是在相同類的其他實例上定義的。JRuby的實現需要把這個屬性(不太明白為什麼叫「開放類」)照搬過來。而這種高級支持只能放在JRuby的運行時中。

7.5.2 編譯器小說

語言的某些特性是由編程環境和高層語言合成的,在底層JVM中根本不存在。這些特性稱為編譯器小說。我們在第6章已經遇到過一個例子了——Java的字符串合併。

提示 你應該瞭解一下這些特性是如何實現的,否則你的代碼可能跑得慢,甚至可能毀掉整個過程。有時運行時環境要做大量的工作來合成某個特性。

Java中的編譯器小說還包括檢查型異常和內部類(如有必要,內部類總會被轉換成帶有特殊合成訪問方法的頂層類,如圖7-5所示)。如果你曾經探究過JAR文件的內部(用jar tvf),見到過許多名字中有$的類,它們就是被取出並轉換成「常規」類的內部類。

圖7-5 用編譯器小說實現的內部類

備選語言也有編譯器小說。某些情況下,這些編譯器小說甚至形成了語言的核心功能。我們來看兩個重要的例子。

1. 函數是一等值

我們在7.1節介紹了函數式編程的關鍵概念——函數應該是能放到變量中的值。這通常說成「函數是一等值」。我們也指出Java對函數的建模方式並不太好。

本書第三部分討論的所有非Java語言都把函數當做一等值。也就是說函數可以放在變量中、傳給方法,並可以像操作其他任何值一樣操作。JVM只能把類當做最小的代碼和功能單元,所以現在所有的非Java語言都用小型匿名類作為函數的載體(不過這在Java 8中可能有所改變)。

解決源碼和JVM字節碼之間這種差異的辦法是,記住對像只是把數據和操作數據的方法綁在一起的東西。請想像一個沒有狀態、只有一個方法的對象,比如第4章Callable接口的匿名實現類。把這樣一個對像放到變量中,作為參數傳遞,然後調用它的call方法,這一切都很正常,像這樣:

Callable<String> myFn = new Callable<String> {
  @Override
  public String call {
    return \"The result\";
  }
};

try {
  System.out.println(myFn.call);
} catch (Exception e) {
}
  

我們把異常處理忽略掉了,因為在這個例子中myFncall方法不可能拋出異常。

注意 在這個例子中,myFn變量是一個匿名類型,所以在編譯後它看起來應該是個類似NameOfEnclosingClass$1.class的東西。類的序號從1開始,並且編譯器每遇到一個就加1。如果它們是動態創建的,並且數量很多(就像在JRuby語言中那樣),會對存放類定義的PermGen內存區域造成壓力。

儘管Java對此沒有任何特殊的語法,但Java程序員經常用這個技巧創建匿名實現類。這就是說結果可能會有點冗長。我們所討論的所有語言都提供了編寫這些函數值(也叫函數字面值、匿名函數)的特殊語法。它們是函數式編程風格的支柱,Scala和Clojure做得都不錯。

2. 多繼承

還有一個例子,在Java(和JVM)中沒辦法表示實現的多繼承。實現多繼承的唯一辦法就是使用接口,可它不允許有任何具體的方法。

相反,在Scala中特性機制(trait)允許把方法的實現混合到類中,所以它提供了不同的繼承視圖。我們會在第9章全面介紹。現在只要記住這種行為必須由Scala的編譯器和運行時合成,在VM層面不提供這種特性。

對JVM上可用的不同類型的語言及其獨特功能的實現方式就介紹到這裡。