一、概述
java語言的“編譯器”其實是一段“不确定”的操作過程,因為他可能是指一個前端編譯器把.java檔案轉變成.class檔案,也可能是指虛拟機後端運作期編譯器(JIT編譯器)把位元組碼變成機器碼的過程,還可能是指靜态提前編譯器(AOT編譯器)直接把.java檔案編譯成本地機器代碼的過程,下面來具體說說
- 前端編譯器:Sun的javac、Eclipse JDT中的增量式編譯器
- JIT編譯器LHotSpot VM的C1、C2編譯器
- AOT編譯器:GNU Compiler for the Java(GCJ)、Excelsior JET
本章節所說的編譯器指的是第一種,因為javac這類編譯器對代碼的運作效率幾乎沒有任何優化措施,是以虛拟機的設計團隊把對性能的優化集中到了後端的即時編譯器中,這樣可以讓那些不是由javac産生的Class檔案(如JRuby、Groovy等語言的Class檔案)也同樣能享受編譯器優化所帶來的好處。但是javac做了許多針對java語言編譯過程的優化措施來改善程式員的編碼風格和提高編碼效率,下面來看具體看看javac。
二、Javac編譯器
Javac編譯器是由java語言編寫的程式,是以這為純java程式員了解他的編譯過程帶來了極大的便利。
1.Javac的源碼與調試
從Sun Javac的代碼來看,編譯的過程主要是下面3個階段
- 解析與填充符号表過程
- 插入式注解處理器的注解處理過程
- 分析與位元組碼生成過程
2.解析與填充符号表
- 詞法分析、文法分析:詞法分析是将源代碼的字元流轉變為标記(Token)集合,單個字元是程式編寫過程的最小元素,而标記則是編譯過程的最小元素,關鍵字、變量名、字面量、運算符都可以成為标記。文法分析是根據Token序列構造抽象文法樹的過程,抽象文法樹是一種用來描述程式代碼文法結構的樹形表示方式,文法樹的每一個節點都代表着程式代碼中的一個文法結構。
- 填充符号表:符号表是由一組符号位址和符号資訊構成的表格,讀者可以把它想象成哈希表中的K-V值對的形式,符号表中所登記的資訊在編譯的不同階段都要用到。在語義分析中,符号表所登記的内容将用于語義檢查和産生中間代碼。在目标代碼生成階段,當對符号名進行位址配置設定時,符号表是位址配置設定的依據。
3.注解處理器
在JDK1.5之後,Java語言提供了對注解的支援,這些注解與普通的Java代碼一樣,是在運作期間發揮作用的,在JDK1.6中實作了JSR-269規範,提供一組插入式注解處理器的标準API在編譯期間對注解進行處理,我們可以把它看做是一組編譯器的插件,在這些插件裡,可以讀取、修改、添加抽象文法樹中的任意元素。如果這些插件在處理注解期間對文法樹進行了修改,編譯器将回到解析及填充符号表的過程重新處理,直到所有插入式注解處理器都沒有再對文法樹進行修改為止,每一次循環稱為一個Round。
4.語義分析和位元組碼生成
文法分析之後,編譯器獲得了程式碼的抽象文法樹表示,文法樹能表示一個結構正确的源程式的抽象,但是無法保證源程式是符合邏輯的。而語義分析的主要任務是對結構上正确的源程式進行上下文有關性質的審查。
- 标注檢查:标注檢查步驟檢查的内容包括諸如變量使用前是否已被聲明、變量與指派之間的資料類型是否能夠比對等。
- 資料及控制流分析:是對程式上下文邏輯的更進一步驗證,它可以檢查出諸如程式局部變量在使用前是否有指派、方法的每條路徑是否都有傳回值、是否所有的受檢查異常都能被正确處理了的問題。
- 解文法糖:文法糖,也稱糖衣文法,指在計算機語言中添加的某種文法,這種文法對語言的功能并沒有影響,但是更加友善程式員使用,通常來說,使用文法糖能夠增加程式的可讀性,進而減少程式代碼出錯的機會。而不支援這些文法,就是解文法糖。
- 位元組碼生成:位元組碼生成是Javac編譯過程的最後一個階段,位元組碼生成階段不僅僅是把前面的各個步驟所生成的資訊轉化成位元組碼寫到磁盤中,編譯器還進行了少量的代碼添加和轉換工作。
三、Java文法糖的味道
1.泛型與類型擦除
泛型本質上是參數化類型的應用,這種參數類型可以用在類、接口和方法的建立中,分别稱為泛型類、泛型接口和泛型方法。其實泛型知識存在于源碼中,編譯後位元組碼檔案中就已經替換成了原生類型,并在相應的地方插入了強制轉型代碼,如果利用反編譯工具進行編譯後,發現泛型不見了,這就是泛型擦除。
2.自動裝箱、拆箱與周遊循環
這些可以說是java語言中使用最多的文法糖,但是不見得什麼情況用都是合适的。
3.條件編譯
java語言中條件編譯的實作,也是java語言中的一顆文法糖,根據布爾常量值的真假,編譯器将會把分支中不成立的代碼消除掉,這一工作将在編譯器接觸文法糖階段。
四、實戰
略