HelloWorld,學(xué)習(xí)每門語言的第一步。有人戲稱,這些年的編程生涯就是學(xué)習(xí)各種語言的HelloWorld,不知是自謙還是自嘲。目前所在的公司使用Java作為主要開發(fā)語言,我進行語言轉(zhuǎn)換也大半年了,這HelloWorld便是語言轉(zhuǎn)換的第一關(guān)。好在本科的時候?qū)W過那么一點,而且在此之前進行了較長時間的C/C++開發(fā),其間有不少的相似之處。這里略去JDK的安裝和環(huán)境配置(JDK為1.6.0.45),直接從代碼入手。
首先看一個最簡單的Java下的HelloWorld:
public class HelloWorld { public static void main(String[]agrs) { System.out. 一般來說,初學(xué)者寫HelloWorld到這里,編譯完運行一下看到結(jié)果就可以結(jié)束了。下面對這個小程序進行更多的探索,進一步了解和學(xué)習(xí)Java編程中的特性。
1.源碼文件的編碼 最初為了簡單起見,我是在Win7中用記事本編寫并保存代碼為HelloWorld.java,然后用命令行直接javac編譯。出于在Windows下寫linux程序的習(xí)慣,我在記事本保存時將代碼保存為UTF-8編碼的HelloWorld.java文件。編譯時提示:

在仔細檢查源代碼確定沒有任何拼寫錯誤后,嘗試將編碼改回Windows默認(rèn)的ANSI,成功生成了HelloWorld.class并能夠正確運行,看來是編碼不一致惹得禍。接下來,抱著嘗試的心態(tài),使用Unicode和Unicode Big Endian保存源碼,發(fā)現(xiàn)也會報錯,只是提示不同,編譯器提示有非法字符。這個問題如果在Eclipse中用默認(rèn)方式保存文件,則不會出現(xiàn)。
有趣的是,如果使用Java的I/O方法生成文本文件,應(yīng)該如何確定文件的編碼,也是一個常見的問題。如果僅僅是涉及Windows/Linux兩個平臺之間的編碼差異,而不包括中文編碼,前者使用/r/n,而后者使用/n/r或/n即可。對于漢字編碼,需要在使用到的I/O方法中指定編碼,這里不再做一步的詳述。
2.為什么沒有import語句? 還記得經(jīng)典的K&R中經(jīng)典的HelloWorld么?即使極盡精簡,C中仍然避免不了使用#include <stdio.h>來引入頭文件,才能使用printf函數(shù)。
而Java和C/C++不一樣,這個簡單的HelloWorld不需要類似include的import,也不需要使用命名空間,看似更簡單了些。實際上,這是因為Java給每個Java文件都默認(rèn)導(dǎo)入了java.lang這個包,從而省去了import java.lang;這個語句罷了。這樣,下面進行屏幕輸出直接使用System.out.println()即可。
java.lang中包括的都是常用的類和方法,具體內(nèi)容讀者有興趣可以自行查閱。上文提到j(luò)ava.import是“默認(rèn)導(dǎo)入”,有沒有什么辦法禁止其導(dǎo)入?我搜索了下,目前還沒有查到相關(guān)的資料,如果哪位讀者了解,希望能告訴我。(這可能涉及到類加載器的問題,暫未進行研究)
如果你執(zhí)意在這段簡單的代碼中使用與import對應(yīng)的package,可以參考本文第六節(jié)。
3.文件名為什么要與類名一致?類名與修飾符問題 在實踐中可以看出,編譯結(jié)果是HelloWorld.class,但是運行的命令卻是java HelloWorld。如果這個文件還有更多的類,可以看到這些類在編譯時都生成了*.class文件。對于“類名和文件名一致”這個疑問提的并不合理,顯然代碼編寫時,一個文件中可以有很多個類。這涉及到了Java的特性(來自《Java編程思想(第四版中文版)》):
每個編譯單元(文件)只能最多有一個public類;如果有,其名稱必須與含有這個編譯單元的文件名相匹配,包括大小寫。
如果不遵守這個要求,寫出類似下面的代碼
//ERROR IN CODEpublic class HelloWorld { public static void main(String[]agrs) { System.out.println("HelloWorld!"); }}public class HelloWorld2 { public static void main(String[]agrs) { System.out.println("HelloWorld, me too!"); }} 那么編譯器會提示這一點

如果把HelloWorld2類的public去掉,將使其變成包訪問權(quán)限,程序可以正常運行,此時只執(zhí)行HelloWorld.main(),并不會發(fā)生沖突。
實際上,如果這個文件只有一個HelloWorld類,或者有兩個類,只要這個包括main()的類名與文件名一致,類名前不加public也是可以正常運行的,且調(diào)用的是與文件名一致的類的main()方法。但個人認(rèn)為這不是良好的編程實踐,如下:
class HelloWorld { public static void main(String[]agrs) { System.out.println("HelloWorld!"); }}class HelloWorld2 { public static void main(String[]agrs) { System.out.println("HelloWorld, me too!"); }} 編譯時將生成HelloWorld.class和HelloWorld2.class,分別運行時,結(jié)果為兩個類各自的main()方法。
4.main()函數(shù)的參數(shù)表和修飾符 在C中,對于main()的修飾符和參數(shù)表有著很多細節(jié)要注意(可以參考五花八門的main())。對于Java,這里對main()的寫法也進行簡單的探究。
先來看參數(shù)表String args[]。雖說編譯器要求必須是這種形式,但如果不用標(biāo)準(zhǔn)形式而用其他形式如int x、String s作為參數(shù)表,編譯是可以通過的,但是在執(zhí)行時則會拋出異常,無論是否提供了參數(shù):

NoSuchMethodError表名,期望的是參數(shù)為String args[]的main()方法。雖然提供了同名方法,由于方法的重載機制,并不能代替期望的main(String args[])方法。
接下來看修飾符public。在第3條已經(jīng)提到了public對于類名的修飾有所說明,而對于main()這個與文件同名的類的成員方法,為了能被調(diào)用,只能用public修飾。不使用修飾符(包訪問權(quán)限)、使用private或protected都會提示:

對于修飾符static,表明這個方法是在存儲在靜態(tài)存儲區(qū)的,不需要實例化對象就可以調(diào)用。去掉static后,可以編譯通過,運行時提示

為了進一步驗證這一點,可以編寫構(gòu)造方法來驗證。(構(gòu)造方法是在類的對象在實例化時會被調(diào)用的方法)
public class HelloWorld { HelloWrold { System.out.println("Constructor"); } public static void main(String[]agrs) { System.out.println("HelloWorld!"); }} 編譯運行時,可以看到構(gòu)造方法并沒有運行。
對于修飾符void,也是必須的。改成int等并加上對應(yīng)的return語句同樣會提示“NoSuchMethodError: main”。在《Java虛擬機規(guī)范(JavaSE7)》(周志明等譯)中介紹到
Java虛擬機的啟動是通過引導(dǎo)類加載器(Bootstrap Class Loader §5.3.1)創(chuàng)建一個初始類(Initial Class)來完成,這個類是由虛擬機的具體實現(xiàn)指定。緊接著,Java虛擬機鏈接這個初始類,初始化并調(diào)用它的public void main(String[])方法。之后的整個執(zhí)行過程都是由對此方法的調(diào)用開始。
可見,void返回值也是被要求的,其他形式是不允許的。
經(jīng)過進一步的測試可知,args[0]是第一個參數(shù);而在C中,argv[0]是執(zhí)行的程序名。
5.既然main()方法是類方法…… 既然main()方法是類方法,那么在實例化這個類的對象時,自然可以再次調(diào)用這個方法。對HelloWorld源代碼加對應(yīng)的兩行,如下所示
public class HelloWorld { public static void main(String[] args) { HelloWorld h = new HelloWorld(); System.out.println("HelloWorld!"); h.main(args); }}運行結(jié)果為
HelloWorld!
HelloWorld!
HelloWorld!
... ...HelloWorld!Exception in thread "main" java.lang.StackOverflowErrorat sun.nio.cs.ext.DoubleByteEncoder.encodeLoop(Unknown Source)at java.nio.charset.CharsetEncoder.encode(Unknown Source)at sun.nio.cs.StreamEncoder.implWrite(Unknown Source)at sun.nio.cs.StreamEncoder.write(Unknown Source)at java.io.OutputStreamWriter.write(Unknown Source)at java.io.BufferedWriter.flushBuffer(Unknown Source)at java.io.PrintStream.write(Unknown Source)at java.io.PrintStream.print(Unknown Source)at java.io.PrintStream.println(Unknown Source)at main.HelloWorld.main(HelloWorld.java:15)at main.HelloWorld.main(HelloWorld.java:16)at main.HelloWorld.main(HelloWorld.java:16)at main.HelloWorld.main(HelloWorld.java:16)... ...
可見HelloWorld被玩壞了,這個無限遞歸創(chuàng)建對象的過程導(dǎo)致了內(nèi)存溢出。
6.試試package 當(dāng)然,使用java更多的時候往往要處理多個文件。為了組織同一命名空間下的文件,需要使用包來進行。對應(yīng)于import,為了指定當(dāng)前文件在哪個包,需要加上package語句。隨便加上一個包名,最初的代碼變成了
import test;public class HelloWorld { public static void main(String[]agrs) { System.out.println("HelloWorld!"); }}編譯后,卻無法運行,如下圖所示

其實,包名是隱含目錄結(jié)構(gòu)的。為了運行,需要把HelloWorld.class移入這個路徑的test文件夾,按照下面的方式運行才可以:

小結(jié) 可見,對于一個小小的HelloWorld,還是有不少東西可以發(fā)掘,只是限于篇幅和本人水平,本文僅僅進行了簡要的介紹。以下是本文提出的可以在后續(xù)學(xué)習(xí)中繼續(xù)深入的主題,僅供參考:
1.I/O方法編碼方式的選擇
2.包和代碼組織
3.Java虛擬機(JVM)
相關(guān)閱讀 深入理解Java HelloWorld
新聞熱點
疑難解答