一胎署、簡單嘮嘮JAVAC
javac這個(gè)命令,搞java的都不陌生窑滞,很多人在第一次用java輸出hello world時(shí)琼牧,都接觸過這個(gè)命令,它的作用簡單來說哀卫,就是將指定的java源代碼編譯成class文件巨坊,后來我們有了Eclipse等IDE工具,就很少關(guān)心這個(gè)命令了此改,偶爾需要的時(shí)候趾撵,才想起來javac xx.java一下,其實(shí)我也一樣共啃。我也認(rèn)為javac命令很簡單占调,不就是編譯個(gè)class文件嗎暂题。但是隨著對jvm的了解,我才覺得妈候,jdk中提供的很多命令都是很有意思的敢靡。javac這個(gè)命令也遠(yuǎn)沒有我想想的那么簡單。
比如苦银,我在介紹javap命令時(shí)啸胧,如果對直接使用javac編譯的class文件,進(jìn)行javap xxx查看其反匯編信息時(shí)幔虏,看不到方法的局部變量表纺念、代碼行與指令行的偏移映射表等信息。再比如想括,jdk8之前陷谱,并沒有提供通過反射獲得方法中入?yún)⒌膮?shù)名的api(如何方法入?yún)?shù)名,可以參考我的文章)等等瑟蜈。
接下來烟逊,我就根據(jù)我查到的資料和感悟來簡單的說說javac命令。
二铺根、JAVAC命令的工作過程
我們都知道javac命令的作用是將java源碼編譯成二進(jìn)制字節(jié)碼class文件宪躯,那么從java源文件編譯成class文件這個(gè)過程中JAVAC命令都進(jìn)行了什么操作呢,或者說JAVAC命令的工作過程是什么樣的呢位迂?
首先访雪,來看看編譯原理(上學(xué)時(shí)學(xué)過,現(xiàn)在基本都還給老師了掂林,網(wǎng)上查了查資料)中編譯過程主要經(jīng)歷以下階段:
javac命令在進(jìn)行編譯操作時(shí),也會按照類似的過程進(jìn)行:
(1)詞法分析階段
詞法分析泻帮,就是將獲得的java源代碼信息轉(zhuǎn)化為標(biāo)記(Token)集合精置,比如關(guān)鍵字、變量刑顺、運(yùn)算符等等氯窍,都是一個(gè)個(gè)的標(biāo)記。詞法分析的過程就是將這些標(biāo)記解析出來蹲堂。
(2)語法分析階段
語法分析是在詞法分析得到的標(biāo)記集合的基礎(chǔ)上狼讨,抽象出對應(yīng)的語法樹。什么是語法樹呢柒竞?簡單的來說政供,一個(gè)java源文件中包信息,import信息、類定義信息布隔、方法信息离陶、字段信息等待作為一個(gè)個(gè)的項(xiàng),這些項(xiàng)集合在一起就抽象為一棵語法樹衅檀。
為了更直觀的解釋什么是語法樹招刨,這里做個(gè)測試,首先在eclipse中創(chuàng)建一個(gè)Test1.java文件哀军,在這個(gè)java文件中添加兩個(gè)類Test1和Test2沉眶,內(nèi)容如下:
package com.test.map;
import java.util.Date;
public class Test1 {
private String name;
public static final int age = 20;
public void test(String username){
this.name = username;
System.out.println(new Date());
}
public void test2(int a){
try {
System.out.println(a/0);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
class Test2{
public void tst(){
}
}
然后使用EClipse的AST插件(AST插件安裝)分析當(dāng)前java源碼的語法樹,內(nèi)容如下杉适,一目了然谎倔,很清晰,包含了這個(gè)java文件package信息猿推、import引入的依賴信息片习,定義的類,類的字段和方法等等信息:
(3)符號表填充階段
符號表(SymbolTable)是由一組符號地址和符號信息構(gòu)成的表格蹬叭,可以把它想象成哈希表中K-V值對的形式(實(shí)際上符號表不一定是哈希表實(shí)現(xiàn)藕咏,可以是有序符號表、樹狀符號表秽五、棧結(jié)構(gòu)符號表等)侈离。符號表中所登記的信息在編譯的不同階段都要用到。在語義分析中筝蚕,符號表所登記的內(nèi)容將用于語義檢查(如檢類型是否匹配等)和產(chǎn)生中間代碼。在目標(biāo)代碼生成階段铺坞,當(dāng)對符號名進(jìn)行地址分配時(shí)起宽,符號表是地址分配的依據(jù)。
(4)注解處理階段
在jdk1.5之后济榨,java提供了對注解的支持坯沪,注解可以在編譯、類加載擒滑、運(yùn)行時(shí)被讀取腐晾,并執(zhí)行相應(yīng)的處理。通過使用注解丐一,開發(fā)人員可以在不改變原有邏輯的情況下藻糖,在源文件中嵌入一些補(bǔ)充信息。如果有需要在編譯期間被處理的注解库车,則這些注解將會在當(dāng)前階段進(jìn)行讀取和處理巨柒。
(5)語義分析
在語法分析之后,編譯器獲得了程序源碼的抽象語法樹,語法樹能表示一個(gè)結(jié)構(gòu)正確的源程序的抽象洋满,但無法保證源程序是符合邏輯的晶乔。而語義分析的主要任務(wù)是對結(jié)構(gòu)上正確的源程序進(jìn)行上下文有關(guān)性質(zhì)的審查,如進(jìn)行類型審查牺勾。舉個(gè)例子正罢,假設(shè)有如下的3個(gè)變量定義語句:
int a = 1;
boolean b = false;
char c = 3;
后續(xù)的代碼中可能出現(xiàn)操作賦值運(yùn)行:
int d = a+c;
int e = b+c;
char f = a+b;
上面的代碼中,它們都能構(gòu)成結(jié)構(gòu)正確的語法樹驻民,但是只有第1種的寫法在語義上是沒有問題的翻具,能夠通過編譯,其余兩種在Java語言中是不合邏輯的川无,無法編譯(在java中int類型可以和char呛占、short、byte類型進(jìn)行加減等操作懦趋,但不能和boolean進(jìn)行相關(guān)操作)晾虑。
javac的編譯過程中,語義分析過程分為標(biāo)注檢查、數(shù)據(jù)及控制流分析、解除語法糖3個(gè)步驟醉蚁。
1件已、標(biāo)注檢查
標(biāo)注檢查,主要包括諸如變量使用前是否已被聲明石景、變量與賦值之間的數(shù)據(jù)類型是否能夠匹配等。在標(biāo)注檢查步驟中,還有一個(gè)重要的動作稱為常量折疊竟痰,如果我們在代碼中寫了如下定義:
int a = 1+2;
那么在語法樹上仍然能看到字面量“1”、“2”以及操作符“+”掏呼,但是在經(jīng)過語義分析階段的常量折疊之后坏快,它們將會被折疊為字面常量“3”。
2憎夷、數(shù)據(jù)及控制流分析
數(shù)據(jù)及控制流分析是對程序上下文邏輯更進(jìn)一步的驗(yàn)證莽鸿,它可以檢查出諸如程序局部變量在使用前是否有賦值、方法的每條路徑是否都有返回值拾给、是否所有的受查異常都被正確處理了等問題祥得。編譯時(shí)期的數(shù)據(jù)及控制流分析與類加載時(shí)的數(shù)據(jù)及控制流分析的目的基本上是一致的,但校驗(yàn)范圍有所區(qū)別蒋得,有一些校驗(yàn)項(xiàng)只有在編譯期或運(yùn)行期才能進(jìn)行级及。
3、解除語法糖
語法糖窄锅,簡單的來說创千,就是在開發(fā)語言中添加某些語法缰雇,這些語法對開發(fā)人員是非常友好和有用的,主要用來使用的開發(fā)語義更易用追驴、開發(fā)人員開發(fā)出的代碼有更好的可讀性械哟、減少程序代碼出錯率等。但是這些語法對開發(fā)語言的功能和性能并沒有太大的影響殿雪。
舉個(gè)例子暇咆,java中基本類型和其對應(yīng)引用類型直接的拆箱裝箱操作,在我們的代碼中我們可以這樣寫:
int a = 1;
Integer b = a+2;
實(shí)際上丙曙,在編譯之后,通過gui等class反編譯工具爸业,可以看到:
int a = 1;
Integer b = Integer.valueOf(a+2);
在java中,還提供了for(i:xx)循環(huán)遍歷亏镰、支持string的switch case扯旷、泛型、變長參數(shù)等等語法糖索抓。關(guān)于java中的語法糖钧忽,后續(xù)會出一篇博客,專門講述逼肯。
解除語法糖階段耸黑,就是將我們代碼中java提供的語法糖解析還原為java原本的基礎(chǔ)語法結(jié)構(gòu)。因?yàn)樵谶\(yùn)行期間篮幢,jvm是不支持這些語法糖對應(yīng)的語法的大刊。
(6)字節(jié)碼生成階段
字節(jié)碼生成階段,會將前面生成的語法樹三椿、符號表等信息轉(zhuǎn)化為字節(jié)碼輸出到磁盤中缺菌,并且會進(jìn)行相關(guān)的代碼添加和轉(zhuǎn)換工作。
比如搜锰,當(dāng)我們使用javap查看反匯編代碼時(shí)男翰,會看到通過new創(chuàng)建對象時(shí),實(shí)際上是調(diào)用了這個(gè)對象的<init>方法纽乱,完成對象的初始化,這個(gè)<init>方法就是在字節(jié)碼生成階段添加的昆箕,它會將我們在代碼中寫的普通語句塊鸦列、成員變量初始化、調(diào)用父類構(gòu)造器等等操作都放入到<init>方法中鹏倘,完成對象的初始化操作薯嗤。
再比如,多個(gè)字符串變量相加a+b+c纤泵,實(shí)際上是創(chuàng)建了一個(gè)StringBuilder對象骆姐,對這些字符串變量進(jìn)行append()操作镜粤,這些通過javap都能看到。
關(guān)于javap的使用玻褪,可以參考我的博客《通過javap命令分析java匯編指令》肉渴。
以上,就是javac命令工作的過程带射,下面講述一下javac命令的使用同规。
三、JAVAC命令的使用
一般安裝好jdk后窟社,我們會在控制臺執(zhí)行javac命令券勺,以驗(yàn)證jdk是否安裝成功,比如灿里,我在我本地執(zhí)行javac关炼,會輸出一下內(nèi)容:
用法: javac <options> <source files>
其中, 可能的選項(xiàng)包括:
-g 生成所有調(diào)試信息
-g:none 不生成任何調(diào)試信息
-g:{lines,vars,source} 只生成某些調(diào)試信息
-nowarn 不生成任何警告
-verbose 輸出有關(guān)編譯器正在執(zhí)行的操作的消息
-deprecation 輸出使用已過時(shí)的 API 的源位置
-classpath <路徑> 指定查找用戶類文件和注釋處理程序的位置
-cp <路徑> 指定查找用戶類文件和注釋處理程序的位置
-sourcepath <路徑> 指定查找輸入源文件的位置
-bootclasspath <路徑> 覆蓋引導(dǎo)類文件的位置
-extdirs <目錄> 覆蓋所安裝擴(kuò)展的位置
-endorseddirs <目錄> 覆蓋簽名的標(biāo)準(zhǔn)路徑的位置
-proc:{none,only} 控制是否執(zhí)行注釋處理和/或編譯。
-processor <class1>[,<class2>,<class3>...] 要運(yùn)行的注釋處理程序的名稱; 繞過默認(rèn)的搜索進(jìn)程
-processorpath <路徑> 指定查找注釋處理程序的位置
-d <目錄> 指定放置生成的類文件的位置
-s <目錄> 指定放置生成的源文件的位置
-implicit:{none,class} 指定是否為隱式引用文件生成類文件
-encoding <編碼> 指定源文件使用的字符編碼
-source <發(fā)行版> 提供與指定發(fā)行版的源兼容性
-target <發(fā)行版> 生成特定 VM 版本的類文件
-version 版本信息
-help 輸出標(biāo)準(zhǔn)選項(xiàng)的提要
-A關(guān)鍵字[=值] 傳遞給注釋處理程序的選項(xiàng)
-X 輸出非標(biāo)準(zhǔn)選項(xiàng)的提要
-J<標(biāo)記> 直接將 <標(biāo)記> 傳遞給運(yùn)行時(shí)系統(tǒng)
-Werror 出現(xiàn)警告時(shí)終止編譯
@<文件名> 從文件讀取選項(xiàng)和文件名
直接執(zhí)行javac命令匣吊,就等同執(zhí)行javac -help儒拂,它會輸出javac這個(gè)命令的使用規(guī)則,以及可以使用的相關(guān)參數(shù)及參數(shù)的簡介缀去。
3.1侣灶、JAVAC命令的使用格式
在前面執(zhí)行javac輸出的信息中,我們看到其輸出的用法為:
javac <options> <source files>
其中:
options表示我們在使用javac命令時(shí)需要指定的參數(shù)選項(xiàng)缕碎,可以同時(shí)指定多個(gè)參數(shù)褥影,每個(gè)參數(shù)之間使用空格隔開。參數(shù)選項(xiàng)在javac的使用說明中都列出來了咏雌,下一小節(jié)凡怎,會著重講解其中的幾個(gè)參數(shù)。
source files表示我們要編譯的java源碼文件赊抖⊥车梗可以是多個(gè)文件,使用空格隔開氛雪。并且房匆,這些文件文件名都必須以.java結(jié)尾。
比如:
javac -nowarn,-verbose Test.java Test2.java
另外报亩,javac后面的參數(shù)浴鸿、文件等信息并沒有固定的順序,你可以按照自己的意愿隨便指定各個(gè)參數(shù)和文件信息的位置順序弦追,比如:
javac -nowarn Test1.java -verbose
還有一點(diǎn)需要注意:
在前面javac輸出的信息中岳链,最后一項(xiàng):
@<文件名> 從文件讀取選項(xiàng)和文件名
意思是,你可以將參數(shù)選項(xiàng)劲件,文件信息單獨(dú)放到一個(gè)或多個(gè)文件中掸哑,然后執(zhí)行:
javac @xxx文件就可以在執(zhí)行javac命令時(shí)约急,將xxx文件中的內(nèi)容傳遞給javac命令。
例如苗分,有兩個(gè)java文件Test1.java厌蔽,Test2.java。然后新建一個(gè)classmsgs.txt文件俭嘁,文件內(nèi)容為:
Test1.java Test2.java -verbose
然后執(zhí)行下面的命令:
javac -nowarn @classmsg.txt
如果有多個(gè)文件躺枕,可以使用空格隔開,如:
javac -nowarn @classmsgs.txt @classmsgs2.txt
就可以看到供填,Test1和Test2被編譯成class文件拐云,并且在編譯時(shí)會輸出編譯器正在執(zhí)行的操作日志(因?yàn)槭褂昧?verbose參數(shù))。
一般情況下近她,當(dāng)你需要編譯的java文件比較多叉瘩,或者需要設(shè)置的參數(shù)比較多時(shí),亦或者要復(fù)用一些參數(shù)信息時(shí)粘捎,可以將這些java文件名或者參數(shù)項(xiàng)抽取出來薇缅,放到一個(gè)或多個(gè)文件中。這一點(diǎn)很像spring中xml的import攒磨,css中@等在一個(gè)文件中引入其他文件的方式泳桦。看來軟件開發(fā)中娩缰,很多地方都是想通的灸撰。
綜上,javac的實(shí)際使用格式可以歸納為:
javac <options> <source files> @files
3.2拼坎、JAVAC命令中的參數(shù)項(xiàng)
在前面直接指向javac命令輸出的信息中浮毯,我們看到j(luò)avac用到的參數(shù)項(xiàng)有很多,那么這些參數(shù)項(xiàng)都是用來干什么的得呢?這里我按照自己查閱的資料以及自己的實(shí)驗(yàn)和理解泰鸡,做一下講解债蓝。這些參數(shù)項(xiàng)很多,我將它們分為一下幾類:
先寫到這里盛龄,先挖個(gè)坑饰迹,后續(xù)再填。