第10章-早期(編譯期)優(yōu)化

[TOC]

10.2 Javac 編譯器

10.2.1 編譯過(guò)程

  1. 從Sun Javac 的代碼來(lái)看,編譯過(guò)程大致可以分為3個(gè)過(guò)程,分別是:
    • 解析與填充符號(hào)表過(guò)程。
    • 插入式注解處理器的注解處理過(guò)程。
    • 分析與字節(jié)碼生成過(guò)程。
編譯過(guò)程

10.2.2 解析與填充符號(hào)表

  • 解析步驟包括了經(jīng)典程序編譯原理中的詞法分析和語(yǔ)法分析兩個(gè)過(guò)程。

10.2.2.1 詞法、語(yǔ)法分析

  1. 詞法分析是將源代碼的字符流轉(zhuǎn)變?yōu)闃?biāo)記(Token)集合,單個(gè)字符是程序編寫(xiě)過(guò)程的最小元素,而標(biāo)記則是編譯過(guò)程的最小元素,關(guān)鍵字、變量名、字面量、運(yùn)算符都可以成為標(biāo)記
  2. 語(yǔ)法分析是根據(jù) Token 序列構(gòu)造抽象語(yǔ)法樹(shù)的過(guò)程,抽象語(yǔ)法樹(shù)(Abstract Syntax Tree,AST)是一種用來(lái)描述程序代碼語(yǔ)法結(jié)構(gòu)的樹(shù)形表示方式,語(yǔ)法樹(shù)的每一個(gè)節(jié)點(diǎn)都代表著程序代碼中的一個(gè)語(yǔ)法結(jié)構(gòu)(Construct),例如包、類(lèi)型、修飾符、運(yùn)算符、接口、返回值甚至代碼注釋等都可以是一個(gè)語(yǔ)法結(jié)構(gòu)。
  3. 經(jīng)過(guò)語(yǔ)法分析之后,編譯器就基本不會(huì)再對(duì)源碼文件進(jìn)行操作了,后續(xù)的操作都建立在抽象語(yǔ)法樹(shù)之上。

10.2.2.2 填充符號(hào)表

  1. 完成了語(yǔ)法分析和詞法分析之后,下一步就是填充符號(hào)表的過(guò)程。
  2. 符號(hào)表(Symbol Table)是由一組符號(hào)地址和符號(hào)信息構(gòu)成的表格,可以把它想象成哈希表中 K-V 值對(duì)的形式(實(shí)際上符號(hào)表不一定是哈希表實(shí)現(xiàn),可以是有序符號(hào)表、樹(shù)狀符號(hào)表、棧結(jié)構(gòu)符號(hào)表等)。符號(hào)表中所登記的信息在編譯的不同階段都要用到。
  3. 在語(yǔ)義分析中,符號(hào)表所登記的內(nèi)容將用于語(yǔ)義檢查(如檢查一個(gè)名字的使用和原先的說(shuō)明是否一致)和產(chǎn)生中間代碼。在目標(biāo)代碼生成階段,當(dāng)對(duì)符號(hào)名進(jìn)行地址分配時(shí),符號(hào)表是地址分配的依據(jù)。

10.2.3 注解處理器

  1. 在 JDK1.5 之后,Java 語(yǔ)言提供了對(duì)注解(Annotation)的支持,這些注解與普通的 Java 代碼一樣,是在運(yùn)行期間發(fā)揮作用的。
  2. 在 JDK1.6 中提供了一組插入式注解處理器的標(biāo)準(zhǔn)API,在編譯期間對(duì)注解進(jìn)行處理,我們可以把它看做是一組編譯器的插件。在這些插件里面,可以讀取、修改、添加抽象語(yǔ)法樹(shù)中的任意元素
  3. 如果這些插件在處理注解期間對(duì)語(yǔ)法樹(shù)進(jìn)行了修改,編譯器將回到解析及填充符號(hào)表的過(guò)程重新處理,直到所有插入式注解處理器都沒(méi)有再對(duì)語(yǔ)法樹(shù)進(jìn)行修改為止,每一次循環(huán)稱(chēng)為一個(gè)Round。

10.2.4 語(yǔ)義分析與字節(jié)碼生成

  • 語(yǔ)法分析之后,編譯器獲得了程序代碼的抽象語(yǔ)法樹(shù)表示,語(yǔ)法樹(shù)能表示一個(gè)結(jié)構(gòu)正確的源程序的抽象,但無(wú)法保證源程序是符合邏輯的。而語(yǔ)義分析的主要任務(wù)是對(duì)結(jié)構(gòu)上正確的源程序進(jìn)行上下文有關(guān)性質(zhì)的審查。Javac 的編譯過(guò)程中,語(yǔ)義分析過(guò)程分為標(biāo)注檢查以及數(shù)據(jù)及控制流分析兩個(gè)步驟

10.2.4.1 標(biāo)注檢查

  1. 標(biāo)注檢查步驟檢查的內(nèi)容包括諸如變量使用前是否已被聲明、變量與賦值之間的數(shù)據(jù)類(lèi)型是否能夠匹配等。
  2. 在標(biāo)注檢查步驟中,還有一個(gè)重要的動(dòng)作稱(chēng)為常量折疊

10.2.4.2 數(shù)據(jù)及控制流分析

  1. 數(shù)據(jù)及控制流分析是對(duì)程序上下文邏輯更進(jìn)一步的驗(yàn)證,它可以檢查出諸如程序局部變量在使用前是否有賦值、方法的每條路徑是否都有返回值、是否所有的受查異常都被正確處理了等問(wèn)題。
  2. 編譯時(shí)期的數(shù)據(jù)及控制流分析與類(lèi)加載時(shí)的數(shù)據(jù)及控制流分析的目的基本上是一致的,但校驗(yàn)范圍有所區(qū)別,有一些校驗(yàn)項(xiàng)只有在編譯期或運(yùn)行期才能進(jìn)行。

10.2.4.3 解語(yǔ)法糖

  1. 語(yǔ)法糖(Syntactic Sugar),也稱(chēng)糖衣語(yǔ)法,指在計(jì)算機(jī)語(yǔ)言中添加的某種語(yǔ)法,這種語(yǔ)法對(duì)語(yǔ)言的功能并沒(méi)有影響,但是更方便程序員使用。通常來(lái)說(shuō),使用語(yǔ)法糖能夠增加程序的可讀性,從而減少程序代碼出錯(cuò)的機(jī)會(huì)。
  2. Java 中最常用的語(yǔ)法糖主要是前面提到過(guò)的泛型、變長(zhǎng)參數(shù)、自動(dòng)裝箱/拆箱
  3. 但虛擬機(jī)運(yùn)行時(shí)不支持這些語(yǔ)法,它們在編譯階段還原回簡(jiǎn)單的基礎(chǔ)語(yǔ)法結(jié)構(gòu),這個(gè)過(guò)程稱(chēng)為解語(yǔ)法糖。

10.2.4.4 字節(jié)碼生成

  1. 字節(jié)碼生成是 Javac 編譯過(guò)程的最后一個(gè)階段,字節(jié)碼生成階段不僅僅是把前面各個(gè)步驟所生成的信息(語(yǔ)法樹(shù)、符號(hào)表)轉(zhuǎn)化成字節(jié)碼寫(xiě)到磁盤(pán)中,編譯器還進(jìn)行了少量的代碼添加和轉(zhuǎn)換工作。

  2. 實(shí)例構(gòu)造器 <init>() 方法和類(lèi)構(gòu)造器 <clinit>() 方法就是在這個(gè)階段添加到語(yǔ)法樹(shù)之中的。

  3. 這兩個(gè)構(gòu)造器的產(chǎn)生過(guò)程實(shí)際上是一個(gè)代碼收斂的過(guò)程,編譯器會(huì)把如下操作收斂到 <init>()<clinit>() 方法之中,并且保證一定是按先執(zhí)行父類(lèi)的實(shí)例構(gòu)造器,然后初始化變量,最后執(zhí)行語(yǔ)句塊的順序進(jìn)行:

    • 語(yǔ)句塊(對(duì)于實(shí)例構(gòu)造器而言是 “{}” 塊,對(duì)于類(lèi)構(gòu)造器而言是 “static{}” 塊)
    • 變量初始化(實(shí)例變量和類(lèi)變量)
    • 調(diào)用父類(lèi)的實(shí)例構(gòu)造器等操作
  4. 完成了對(duì)語(yǔ)法樹(shù)的遍歷和調(diào)整之后,再把填充了所有所需信息的符號(hào)表轉(zhuǎn)成字節(jié)碼,生成最終的 Class 文件,到此為止整個(gè)編譯過(guò)程宣告結(jié)束。

10.3 Java語(yǔ)法糖的味道

10.3.1 泛型與類(lèi)型擦除

  1. 泛型是 JDK1.5 的一項(xiàng)新增特性,它的本質(zhì)是參數(shù)化類(lèi)型(Parametersized Type)的應(yīng)用,也就是說(shuō)所操作的數(shù)據(jù)類(lèi)型被指定為一個(gè)參數(shù)。這種參數(shù)類(lèi)型可以用在類(lèi)、接口和方法的創(chuàng)建中,分別稱(chēng)為泛型類(lèi)、泛型接口和泛型方法。
  2. Java 語(yǔ)言中的泛型只在程序源碼中存在,在編譯后的字節(jié)碼文件中,就已經(jīng)替換為原來(lái)的原生類(lèi)型(Raw Type,也稱(chēng)為裸類(lèi)型)了,并且在相應(yīng)的地方插入了強(qiáng)制轉(zhuǎn)型代碼,因此,對(duì)于運(yùn)行期的 Java 語(yǔ)言來(lái)說(shuō),ArrayList<int>ArrayList<String> 就是同一個(gè)類(lèi)
  3. 所以泛型技術(shù)實(shí)際上是 Java 語(yǔ)言的一顆語(yǔ)法糖,Java語(yǔ)言中的泛型實(shí)現(xiàn)方法稱(chēng)為類(lèi)型擦除,基于這種方法實(shí)現(xiàn)的泛型稱(chēng)為偽泛型
public class GenericTypes
{
    // 'method(List<String>)' clashes with 'method(List<Integer>)'; both methods have same erasure
    // 編譯錯(cuò)誤,因?yàn)轭?lèi)型擦除后參數(shù)類(lèi)型相同,都是 List<E>

    public static void method(List<String> list)
    {
        System.out.println("invoke method(List<String> list)");
    }

    public static void method(List<Integer> list)
    {
        System.out.println("invoke method(List<Integer> list)");
    }
}
  1. 上述這段代碼是不能被編譯的,因?yàn)閰?shù) List<Integer>List<String> 編譯之后都被擦除了,變成了一樣的原生類(lèi)型 List<E>,擦除動(dòng)作導(dǎo)致這兩種方法的特征簽名變得一模一樣。

10.3.2 自動(dòng)裝箱、拆箱與遍歷循環(huán)

  1. 從純技術(shù)的角度來(lái)講,自動(dòng)裝箱、自動(dòng)拆箱與遍歷循環(huán)(Foreach循環(huán))這些語(yǔ)法糖,無(wú)論是實(shí)現(xiàn)上還是思想上都不能和泛型相比,兩者的難度和深度都有很大差距。但是毫無(wú)疑問(wèn),它們是 Java 語(yǔ)言里使用得最多的語(yǔ)法糖。

  2. 自動(dòng)裝箱、拆箱與遍歷循環(huán)

// 編譯前
public static void main(String[] args) 
{
    List<Integer> list = Arrays.asList(1, 2, 3, 4);
    
    int sum = 0;
    for (int i : list) {
        sum += i;
    }
    System.out.println(sum);
}
// 編譯后
public static void main(String[] args) 
{
    List list = Arrays.asList( new Integer[] {
         Integer.valueOf(1),
         Integer.valueOf(2),
         Integer.valueOf(3),
         Integer.valueOf(4) });

    int sum = 0;
    for (Iterator localIterator = list.iterator(); localIterator.hasNext(); ) {
        int i = ((Integer)localIterator.next()).intValue();
        sum += i;
    }
    System.out.println(sum);
}
  1. 上述代碼包含了泛型、自動(dòng)裝箱、自動(dòng)拆箱、遍歷循環(huán)與變長(zhǎng)參數(shù) 5 種語(yǔ)法糖,以及它們?cè)诰幾g后的變化:
    • 泛型擦除已經(jīng)說(shuō)過(guò)。
    • 自動(dòng)裝箱、拆箱在編譯之后被轉(zhuǎn)化成了對(duì)應(yīng)的包裝和還原方法
    • 遍歷循環(huán)把代碼還原成了迭代器的實(shí)現(xiàn),這也是為何遍歷循環(huán)需要被遍歷的類(lèi)實(shí)現(xiàn)Iterable接口的原因。
    • 變長(zhǎng)參數(shù)在調(diào)用的時(shí)候變成了一個(gè)數(shù)組類(lèi)型的參數(shù),在變長(zhǎng)參數(shù)出現(xiàn)之前,程序員就是使用數(shù)組來(lái)完成類(lèi)似功能的。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容