經過開篇對Java運行機制及相關環境搭建,本篇主要討論Java程序開發的基礎知識點,我簡單的梳理一下。在講解數據類型之前,我順便提及一下Java注釋:單行注釋、多行注釋以及文檔注釋,這里重點強調文檔注釋。通常單行注釋獨占一行並用"//"來開頭,多行注釋會占據多行並用/*...*/來表示。
/* HelloWorld是每一個Java學習者的起點, 需要好好掌握 */ public class HelloWorld { public static void main(String[] args) { // 輸出Hello World! System.out.println("Hello World!"); } }
那什么叫文檔注釋呢?簡單的說,就是利用javadoc將源代碼的文檔注釋生成一份API文檔,供使用者查詢和參考。JDK API的在線文檔查詢:http://dlc.sun.com.edgesuite.net/jdk/jdk-api-localizations/jdk-api-zh-cn/builds/latest/html/zh_CN/api/,從文檔界面不難看出分成三大區:包列表區(左上方)、類列表區(左下方)、詳細說明區(右側)。點擊左側"類列表區"中的類,右側將顯示該類對應的構造函數、屬性、方法等詳細信息。接下來,我們利用javadoc來生成一份API文檔,需要說明的是,javadoc只處理以public或protected修飾的類、接口、方法、屬性、構造器和內部類之前的文檔注釋而忽略其他地方的文檔注釋,如果開發者希望給使用者提供private成員的文檔,則需要加入-private選項。文檔注釋用/**...*/來表示。javadoc的語法如下:
javadoc -d [API文檔存放目錄] -windowtitle [API文檔瀏覽器標題] -doctitle [概述頁面標題] -header [頁面頁眉] ... Java源文件/包 (可利用javadoc -help查詢全部選項)
如源代碼中包含中文,則需要設置-locale, -encoding, -charset如下:
javadoc -d [API文檔存放目錄] -windowtitle [API文檔瀏覽器標題] -doctitle [概述頁面標題] -header [頁面頁眉] -encoding utf-8 -charset utf-8 -locale zh_CN ... Java源文件/包 (可利用javadoc -help查詢全部選項)
同時,為了更詳細說明類、方法等成員的文檔信息,經常添加以下javadoc標記(如@author、@version、@param、@return等)到這些成員上加以說明。
package miracle; /** * Description: * <br/>此程序主要測試Javadoc指令來生成文檔注釋 * <br/>程序名:TestJavadoc.java * <br/>編寫日期:2012-08-22 * @author Miracle, He miracle@sina.com * @version 1.0 */ public class TestJavadoc { /** * 測試屬性 */ protected String name; /** * 主方法,程序入口
* @param args 輸入參數列表 */ public static void main(String[] args) { System.out.println("Hello, Miracle!"); } }
package miracleHe; /** * Description: * <br/>此程序主要測試Javadoc指令來生成文檔注釋 * <br/>程序名:TestJavadocTag.java * <br/>編寫日期:2012-08-22 * @author Miracle, He miracle@sina.com * @version 1.0 */ public class TestJavadocTag { /** * 此方法用來打招呼 * @param name 打招呼的名稱 * @return 返回打招呼的字符串 */ private String hello(String name) { return name + ",你好!"; } }
我們這里添加了兩個包miracle和miracleHe(目的為生成概述),執行以下命令將輸出API文檔。
javadoc -d apidoc -windowtitle "Generate Javadoc" -doctitle "Learning HelloWorld Class by Javadoc" -header "Javadoc Test" -charset "utf-8" -encoding "utf-8" Test*.java
默認情況下不會提取@author、@version等信息,如需提取則需要添加。
javadoc -d apidoc -windowtitle "Generate Javadoc" -doctitle "Learning HelloWorld Class by Javadoc" -header "Javadoc Test"-charset "utf-8" -encoding "utf-8" -author -version Test*.java
Java是強類型語言,也就是變量或表達式在編譯時就已經明確其類型,即先聲明后使用。數據類型分為: 基本類型(Primitive Type)和引用類型(Reference Type)。其組織關系如下:基本類型包含整數類型(byte、short、int、long)、字符類型(char)、浮點類型(float、double)和布爾類型(true、false);引用類型包含字符串(String)、數組、類、接口和空類型(null)。先從整數類型談起,如果一個較小的整數(在byte或short的范圍之內)賦值給byte或short時,系統會自動轉化為對應的類型;如果一個巨大的整數(超出int的范圍),系統則不會自動當做long來進行處理,需要添加后綴L來進行標識,即使這個整數在int的范圍之內聲明的long類型變量不添加L仍然當做是int。
public class TestInteger { public static void main(String[] args) { byte b = 56; //系統會自動轉化為byte //long big = 999999999999; //出錯,系統不會當作long來處理 long big2 = 41433333313243133L; //使用L強制為long類型 } }
整數除了十進制來表示外,還可以使用八進制(0開頭)以及十六進制(0x開頭,A~F代表10~15)來進行表示。
int octalValue = 013; int hexValue = 0x2F;
雖然字符型被單獨處理,但其實它就是一種整數(0~65535之間的無符號整數),字符通常可以使用''(如'A')、轉義字符('\n'、'\r')和Unicode('\uXXXX',前256個字符與ASCII碼一致)來表示。如果將0~65535之間的整數賦給char變量將直接將int轉化為char類型。
public class TestChar { public static void main(String[] args) { //定義字符型 char a = 'a'; char enter = '\r'; char ch = '\u24af'; System.out.println(ch);//輸出? char yu = '宇'; int yuValue = yu; System.out.println(yuValue);//23431 char c = 97; System.out.println(c);//a } }
但請注意,字符串雖然是由字符組成(可看作字符數組),但字符串是引用類型。接下來,我們討論一下浮點數。浮點數分為單精度浮點數(float)和雙精度浮點數(double)。其中float占4個字節,第一位是符號位,接下來8位是指數位,最后23位是尾數,必須要添加后綴F;double占8個字節,第一位是符號位,接下來11位是指數位,最后52位是尾數,是默認類型,可以不添加后綴D來標識。浮點數可用十進制(如5.12,.512)和科學計數法(5.12E2)來表示。特別需要注意的是:浮點數還包含三個特殊的值,正無窮大(POSITIVE_INFINITY,通過正數除以0得到)、負無窮大(NEGATIVE_INFINITY,通過負數除以0得到)和非數(NaN,通過0.0除以0.0或對負數開方得到)。所有正無窮大值都相等,所有負無窮大值相等,非數不與任何數相等(包含NaN本身也不相等)。
public class TestFloat { public static void main(String[] args) { float f = 5.12F; double zero = 0.0; float p = Float.POSITIVE_INFINITY; double n = Double.NEGATIVE_INFINITY; System.out.println(p==n);//false System.out.println(f/zero);//Infinity System.out.println(f/zero==p);//true System.out.println(0.0/zero==Double.NaN);//false System.out.println(6.0/0==8.2/0);//true System.out.println(1/0);//拋出異常 } }
另外提一句,bool類型只能為true或false,不能用0或非0來表示,其他基本類型都不能轉化為bool類型,如果bool類型與字符串相連,將直接轉化為字符串。
接下來,我們討論一下數據類型轉換相關知識。數據類型轉換分為自動轉換和強制轉換,自動轉換的關系如下圖:

箭頭左邊的類型可以自動轉化為右邊的類型。此外,當基本類型與字符串進行連接時,基本類型會自動轉換為字符串。反之,將字符串轉化為基本類型則調用xxx.pareseXxx方法(如Integer.parseInt("12"))。
public class AutoConversion { public static void main(String[] args) { int a = 6; float f = a; System.out.println(f);//6.0 byte b = 9; //char c = b;//出錯,byte不能自動轉化為char double d = b; System.out.println(d);//9.0 //基本類型與字符串進行轉化 String s = 5.3F + ""; System.out.println(s);//5.3 System.out.println(3 + 4 + "Hello!");//7Hello! System.out.println("Hello!" + 3 + 4);//Hello!34 } }
反之,如果想把箭頭右邊的類型轉換為左邊的類型,就需要強制類型轉換,這樣做可能導致數據溢出或精度丟失。因此在進行強制類型轉換時需格外小心。
public class ForceConversion { public static void main(String[] args) { int i = 234; byte b = (byte)i; System.out.println(b);//-22 double d = 3.56; int n = (int)d; System.out.println(n);//3 } }
可能大家會問234咋轉換為byte就變成了-22了呢?我們都知道,byte的范圍是-128~127,顯示超出了表示的范圍,234的二進制表示為00..0011101010,轉換后截取后8位之后變成11101010,而第一位是符號位(這里是個負數),而負數在計算機中以補碼形式存在,需要轉換為原碼(補碼減1成反碼再按位取反,符號位不變,因此:11101010-->11101001-->10010110)。
在進行表達式計算時,數據類型會發生自動提升:如所有byte、short和char將自動提升為int類型,表達式的數據類型將提升為最高等級操作數的數據類型。對於整數相除時,即使不能除盡也要舍棄小數部分,對於字符串與數字或字符相加時,此時應該從左自右進行運算,以判斷是否為字符串連接還是加法運算。
public class AutoPremotion { public static void main(String[] args) { short s = 5; //s = s - 2;//出錯,表達式被提升為int byte b = 10; char c = 'a'; double d = .12; System.out.println(b + c + d);//表達式被提升為double System.out.println(23 / 3);//7 System.out.println(b + c + "Hello!");//107Hello! System.out.println("Hello!" + b + c);//Hello!10a } }
在講解流程控制之前,我在這里補充一下平常容易出錯的知識點。我們來看以下程序:
public class CompareString { public static void main(String[] args) { String a = new String("Miracle"); String b = new String("Miracle"); System.out.println(a == b);//false,因此a與b指向不同的實例(盡管內容一致) String c = "Miracle"; String d = "Miracle"; System.out.println(c == d);//true,此處由於字符串緩存機制,比較的僅僅是兩者的內容 } }
另外,對於短路運算符(如||)與不短路運算符(如|)的區別: ||先計算左邊的操作數,如果為true將不再繼續計算,而|不管左邊結果如何都會計算之后的操作數。
public class TestLogicOperator { public static void main(String[] args) { int a = 5; int b = 10; if(a > 4 || b++ > 10) { System.out.println("a=" + a + ",b=" + b);//a=5,b=10 } if(a > 4 | b++ > 10) { System.out.println("a=" + a + ",b=" + b);//a=5,b=11 } } }
接下來,進入流程控制的講解,任何編程語言(Java也不例外)的流程控制結構包含:順序結構和分支結構(if、if...else、if...else if...else、switch)和循環結構(while、do...while、for、foreach)。這里不再舉例說明,只是強調一點,對於分支結構if...else,盡量不要省略之后的花括號,即使只有一條語句也不能省略,其中對於非常簡單的if...else結構,可以用三目運算符(if(a>b)?a:b)來替代。為了避免發生邏輯錯誤,應該遵守:總是優先把包含范圍小的的條件放在前面處理。
public class TestIf { public static void main(String[] args) { int age = 45; if(age > 20) { System.out.println("Young"); } else if(age > 40) { System.out.println("Middle"); } else if(age > 60) { System.out.println("Old"); } } }
我們發現運行之后輸出Young,明顯與預期不符(應該輸出Middle)。就是因為剛才提到的范圍問題導致(age > 20比age > 60范圍大)。我們改寫一下:
public class TestIf { public static void main(String[] args) { int age = 45; if(age > 60) { System.out.println("Old"); } else if(age > 40) { System.out.println("Middle"); } else if(age > 20) { System.out.println("Young"); } } }
剛才提到if之后的花括號不能省略,對於switch...case來說,case之后的花括號盡量省略,而break不要省略(否則將貫穿多個case執行),盡量加上default,此外對於switch(expression)的表達式只能為整數,不能為字符串(這點跟其他語言不一樣)。
對於循環結構,也不要省略循環體中的花括號(即使只有一句),還可以組成多層嵌套循環。可以使用break結束本層循環,進入循環之后的代碼,使用continue結束本次循環,進入下一次循環,也可以使用return直接返回。但是有時還有特殊情況,就是從內層循環跳出到外層循環,需要使用Java標簽(用:表示),不過通常此標簽必須位於break所在循環的外層循環之前才起作用。以下程序將輸出: i = 0,j = 0;i = 0,j = 1。
public class TestBreak { public static void main(String[] args) { outer: for(int i = 0; i < 5; i++) { for(int j = 0; j < 2; j++) { System.out.println("i = " + i + ",j = " + j); if(j == 1) { break outer; } } } } }
此外,continue也可添加標簽,表示立即結束continue所在循環,跳到標簽所在位置進入下一次循環。如果將以上的break改成continue的話,將輸出: i = 0, j = 0; i = 0, j = 1; i = 1, j = 0; i = 1, j = 1; i = 2, j = 0; i = 2, j = 1; i = 3, j = 0; i = 3, j = 1; i = 4, j = 0; i = 4, j = 1。最后,return直接返回整個方法,而不管方法中嵌套有多少層循環。