Java入門記(一):折騰HelloWorld

  HelloWorld,學習每門語言的第一步。有人戲稱,這些年的編程生涯就是學習各類語言的HelloWorld,不知是自謙仍是自嘲。目前所在的公司使用Java做爲主要開發語言,我進行語言轉換也大半年了,這HelloWorld即是語言轉換的第一關。好在本科的時候學過那麼一點,並且在此以前進行了較長時間的C/C++開發,其間有很多的類似之處。這裏略去JDK的安裝和環境配置(JDK爲1.6.0.45),直接從代碼入手。html

  首先看一個最簡單的Java下的HelloWorld:java

public class HelloWorld {
  public static void main(String[]agrs)
    {
      System.out.println("HelloWorld!");
    }
}

  通常來講,初學者寫HelloWorld到這裏,編譯完運行一下看到結果就能夠結束了。下面對這個小程序進行更多的探索,進一步瞭解和學習Java編程中的特性。編程

 

1.源碼文件的編碼

  最初爲了簡單起見,我是在Win7中用記事本編寫並保存代碼爲HelloWorld.java,而後用命令行直接javac編譯。出於在Windows下寫Linux程序的習慣,我在記事本保存時將代碼保存爲UTF-8編碼的HelloWorld.java文件。編譯時提示:小程序

  在仔細檢查源代碼肯定沒有任何拼寫錯誤後,嘗試將編碼改回Windows默認的ANSI,成功生成了HelloWorld.class並可以正確運行,看來是編碼不一致惹得禍。接下來,抱着嘗試的心態,使用Unicode和Unicode Big Endian保存源碼,發現也會報錯,只是提示不一樣,編譯器提示有非法字符。這個問題若是在Eclipse中用默認方式保存文件,則不會出現。函數

  有趣的是,若是使用Java的I/O方法生成文本文件,應該如何肯定文件的編碼,也是一個常見的問題。若是僅僅是涉及Windows/Linux兩個平臺之間的編碼差別,而不包括中文編碼,前者使用\r\n,然後者使用\n\r或\n便可。對於漢字編碼,須要在使用到的I/O方法中指定編碼,這裏再也不作一步的詳述。oop

 

2.爲何沒有import語句?

  還記得經典的K&R中經典的HelloWorld麼?即便極盡精簡,C中仍然避免不了使用#include <stdio.h>來引入頭文件,才能使用printf函數。post

  而Java和C/C++不同,這個簡單的HelloWorld不須要相似include的import,也不須要使用命名空間,看似更簡單了些。實際上,這是由於Java給每一個Java文件都默認導入了java.lang這個包,從而省去了import java.lang;這個語句罷了。這樣,下面進行屏幕輸出直接使用System.out.println()便可。學習

  java.lang中包括的都是經常使用的類和方法,具體內容讀者有興趣能夠自行查閱。上文提到java.import是「默認導入」,有沒有什麼辦法禁止其導入?我搜索了下,目前尚未查到相關的資料,若是哪位讀者瞭解,但願能告訴我。(這可能涉及到類加載器的問題,暫未進行研究)測試

  若是你執意在這段簡單的代碼中使用與import對應的package,能夠參考本文第六節編碼

 

3.文件名爲何要與類名一致?類名與修飾符問題

  在實踐中能夠看出,編譯結果是HelloWorld.class,可是運行的命令倒是java HelloWorld。若是這個文件還有更多的類,能夠看到這些類在編譯時都生成了*.class文件。對於「類名和文件名一致」這個疑問提的並不合理,顯然代碼編寫時,一個文件中能夠有不少個類。這涉及到了Java的特性(來自《Java編程思想(第四版中文版)》):

每一個編譯單元(文件)只能最多有一個public類;若是有,其名稱必須與含有這個編譯單元的文件名相匹配,包括大小寫。

  若是不遵照這個要求,寫出相似下面的代碼

//ERROR IN CODE
public 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去掉,將使其變成包訪問權限,程序能夠正常運行,此時只執行HelloWorld.main(),並不會發生衝突。

  實際上,若是這個文件只有一個HelloWorld類,或者有兩個類,只要這個包括main()的類名與文件名一致,類名前不加public也是能夠正常運行的,且調用的是與文件名一致的類的main()方法。但我的認爲這不是良好的編程實踐,以下:

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,分別運行時,結果爲兩個類各自的main()方法。

 

4.main()函數的參數表和修飾符

  在C中,對於main()的修飾符和參數表有着不少細節要注意(能夠參考五花八門的main())。對於Java,這裏對main()的寫法也進行簡單的探究。

  先來看參數表String args[]。雖然說編譯器要求必須是這種形式,但若是不用標準形式而用其餘形式如int x、String s做爲參數表,編譯是能夠經過的,可是在執行時則會拋出異常,不管是否提供了參數:

  NoSuchMethodError表名,指望的是參數爲String args[]的main()方法。雖然提供了同名方法,因爲方法的重載機制,並不能代替指望的main(String args[])方法。

  接下來看修飾符public。在第3條已經提到了public對於類名的修飾有所說明,而對於main()這個與文件同名的類的成員方法,爲了能被調用,只能用public修飾。不使用修飾符(包訪問權限)、使用private或protected都會提示:

  對於修飾符static,代表這個方法是在存儲在靜態存儲區的,不須要實例化對象就能夠調用。去掉static後,能夠編譯經過,運行時提示

爲了進一步驗證這一點,能夠編寫構造方法來驗證。(構造方法是在類的對象在實例化時會被調用的方法)

public class HelloWorld {
    HelloWrold
    {
        System.out.println("Constructor");
    }
    public static void main(String[]agrs)
    {
      System.out.println("HelloWorld!");
    }
}

  編譯運行時,能夠看到構造方法並無運行。

  對於修飾符void,也是必須的。改爲int等並加上對應的return語句一樣會提示「NoSuchMethodError: main」。在《Java虛擬機規範(JavaSE7)》(周志明等譯)中介紹到

Java虛擬機的啓動是經過引導類加載器(Bootstrap Class Loader §5.3.1)建立一個初始類(Initial Class)來完成,這個類是由虛擬機的具體實現指定。緊接着,Java虛擬機連接這個初始類,初始化並調用它的public void main(String[])方法。以後的整個執行過程都是由對此方法的調用開始。

  可見,void返回值也是被要求的,其餘形式是不容許的。

  通過進一步的測試可知,args[0]是第一個參數;而在C中,argv[0]是執行的程序名。 

 

5.既然main()方法是類方法……

  既然main()方法是類方法,那麼在實例化這個類的對象時,天然能夠再次調用這個方法。對HelloWorld源代碼加對應的兩行,以下所示

public class HelloWorld {
  public static void main(String[] args)
    {
        HelloWorld h = new HelloWorld();
        System.out.println("HelloWorld!");
        h.main(args);
    }
}

運行結果爲

HelloWorld!

HelloWorld!

HelloWorld!

... ...
HelloWorld!
Exception in thread "main" java.lang.StackOverflowError
at 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被玩壞了,這個無限遞歸建立對象的過程致使了內存溢出。

 

6.試試package

  固然,使用java更多的時候每每要處理多個文件。爲了組織同一命名空間下的文件,須要使用包來進行。對應於import,爲了指定當前文件在哪一個包,須要加上package語句。隨便加上一個包名,最初的代碼變成了

package test;

public class HelloWorld {
  public static void main(String[]agrs)
    {
      System.out.println("HelloWorld!");
    }
}

編譯後,卻沒法運行,以下圖所示

  其實,包名是隱含目錄結構的。爲了運行,須要把HelloWorld.class移入這個路徑的test文件夾,按照下面的方式運行才能夠:

   (2015.10.6更新)若是引用了三方jar包,能夠在運行javac和java命令的時候使用-cp指定jar包所在相對路徑,或者直接把jar包放在該class文件所在目錄或環境變量CLASSPATH指定的目錄下。

小結

  可見,對於一個小小的HelloWorld,仍是有很多東西能夠發掘,只是限於篇幅和本人水平,本文僅僅進行了簡要的介紹。如下是本文提出的能夠在後續學習中繼續深刻的主題,僅供參考:

1.I/O方法編碼方式的選擇

2.包和代碼組織

3.Java虛擬機(JVM)

 

相關閱讀

    深刻理解Java HelloWorld

相關文章
相關標籤/搜索