我看Java虛擬機(4)---Javac編譯器

編譯過程分爲三個部分:

  1. 解析與填充符號表
  2. 插入式註解處理器的註解處理過程
  3. 分析和字節碼的生成

解析與填充符號表

詞法語法分析,生成抽象語法樹(AST,Abstract Syntax Tree);
填充符號表

註解處理器

該部分可以操作上一步生成的語法樹,修改一次,解析和填充符號表重新做一次,直到註解處理器再沒有對語法樹進行修改爲止。
《深入理解Java虛擬機》本部分內容的案例分析,就是針對這部分內容,可見其重要程度。(對於本書的案例,讀者將會在介紹完基本知識之後,找時間講解)

語義分析與字節碼生成

標註檢查:重要的一個動作——常量摺疊,int a = 1+2;經過常量摺疊之後,直接存儲int a = 3。
數據及控制流分析:局部變量沒有訪問標誌等信息,則在方法內聲明final信息,class文件是不會知道局部變量是否被聲明瞭final,這就要靠編譯期來保證。
解語法糖:語法糖,指在計算機語言中添加的某種語法,該語法對於語言功能沒有影響,只是爲了增加程序的可讀性。常見的語法糖

  1. 泛型,如下代碼:
public class Test{
        public void fun(List<String> ls){}
        public void fun(List<Integer> li){} 
     }

可這段代碼是無法編譯的,因爲再解語法糖之後,class文件中就會擦除String和Integer信息,所以兩個方法對於class文件來說完全一樣,這是不被允許的。接着看:

public class Test{
        public int fun(List<String> ls){}
        public void fun(List<Integer> li){} 
     }

神奇的事情發生了,這段代碼編譯通過了,這是因爲class文件判斷兩個方法相同不相同的根據是描述符,而上一節也介紹了描述符(參數列表+返回值),上面兩個方法的描述符分別是:(Ljava/util/List)I,(Ljava/util/List)V,所以他麼是可以在class文件中共存。
2. 拆箱裝箱

Integer a = 1;
Integer b = 2;
Integer c = a + b;

上述代碼自動發生了拆箱來相加,自動裝箱賦值給c。
3. 條件編譯

public static void main(String[] args){
    if(true){
        System.out.println("block1");
    }
    else{
        System.out.println("block2");
    }
}

編譯結果只有block1,if是特殊的,如果使用while(true),則編譯的控制流分析中報錯。

字節碼生成
不僅將前面各個步驟信息轉換爲字節碼,還進行少量代碼添加和轉換工作。比如類構造器()和實例構造器(),這裏的實例構造器不是默認構造器,如果代碼中沒有提供構造器,默認生成的構造器將在填充符號表時完成。
<cinit>()就是將代碼中的“static{}”收集到一塊,其中賦值(static變量)語句在“static{}”之前執行。
<init>()就針對“{}”和實例變量了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章