吃水不忘挖井人,我是根據http://www.iteedu.com/plang/java/javadiary/2.php學習的,所以大部分內容也都是這個網站上的。
1、第一個Java程序
1 public class HelloWorld { 2 public static void main(String[] args){ 3 System.out.println("Hello World!I am CheeseZH!"); 4 } 5 }
寫Java程序通常都是由定義「類別」開始,"class"是Java 用來定義類別的關鍵詞,類別的名稱是HelloWorld,這與您所編輯的檔案(HelloWorld.java)主檔名必須相同,在編寫Java程序 時,一個檔案中可撰寫數個類別,但是只能有一個"public"類別, 而且檔案主檔名必須與這個"public"類別的名稱相同。
接下來看看 main() 方法(Method),它是Java程序的「進入點」(Entry point), 程序的執行是由進入點開始的:
1)、它一定是個"public"成員(Member), 這樣它才可以被呼叫;
2)、它不需要產生對象就要能被執行,所以它必須是個"static"成員。
"void"表示這個方法執行結束后不傳回任何值,Java程序的主 方法不需傳回任何值,所以一律使用void;main()是Java程序的 主方法名稱,其中"String[] args"是命令列自變量 (Command line argument),可以在執行程序時取得使用者指定的相關參數,目前雖然您不使用,但仍要撰寫它,這是規定。
System.out.println("Hello World!I am CheeseZH!");
在這個程序中使用了java.lang套件下的System類別,使用它的公開成員 out 對象,它是一個 PrintStream 對象,您使用了它所提供的println()方法,將當中指定的字符串(String) "Hello World!I am CheeseZH!"輸出至 Console 上。
注意在Java中字符串要使用""包括,println()表示輸出字符串后自動斷行,如果使用print()的話,則輸出字符串后程序並不會自動斷行;注意陳 述結束要用 ';' 。
2、C風格輸出printf
public class HelloWorld { public static void main(String[] args){ System.out.printf("%s\n", "Hello World!I am CheeseZH!"); } }
3、獲取用戶輸入
1)、System.in.read()使用不方便
當在文字模式下要輸入數據至程序中時,您可以使用標准輸入串流對象System.in,然而我們很少直接使用它,因為System.in對象所提供的 read()方法,是從輸入串流取得一個字節的數據,並傳回該字節的整數值。
在文字模式下的輸入是以字符的方式傳送給程序,所以直接使用read()方法取得的是字符的ASCII編碼整數,通常要取得的使用者輸入會是一個字符串,或是一組數字,所以 System.in對象的read()方法一次只讀入一個字節數據的方式並不適用。
2)、使用java.util.Scanner
import java.util.Scanner; public class ScannerInput { public static void main(String[] args){ Scanner scanner = new Scanner(System.in); System.out.print("Please input your name:"); System.out.printf("Hello, %s.\n", scanner.next()); System.out.print("Please input a number:"); System.out.printf("%d\n", scanner.nextInt()); } }
"new"表示新增一個Scanner對象,在新增一個 Scanner對象時需要一個System.in對象,因為實際上還是System.in在取得使用者的輸入,您可以將Scanner看作是 System.in對象的支持者,System.in取得使用者輸入之后,交給Scanner作一些處理(實際上,這是 Decorator 模式 的一個應用)。
簡單的說,您告訴執行環境新增一個Scanner對象,然后使用它的next()方法來取得使用者的輸入字符串,使用 Scanner對象的nextInt()方法取得數字。同樣的,您還可以使用Scanner的nextFloat()、nextBoolean()等方法來取得使用者的輸入,並轉換為正確的 數據型態。
要注意的是,Scanner取得輸入的依據是空格符,舉凡按下空格鍵、tab鍵或是enter鍵,Scanner就會傳回下一個輸入,如果您想要取得包 括空格符的輸入,比較簡單的方法是 使用 BufferedReader 類別取得輸入。
3)、使用 BufferedReader 類別取得輸入
(1)、BufferedReader類別,它是java.io套件中所提供的一個類別,所以使用這個類別時必須先import java.io套件;
(2)、使用BufferedReader對象的readLine()方法必須處理IOException例外(exception),例外處理機制是Java提 供給程序設計人員捕捉程序中可能發生的錯誤所提供的機制,現階段您處理IOException的方法是在main()方法后,加上 throws IOException,這在以后會再詳細討論為何要這么作。
BufferedReader在建構時接受一個Reader對象,在讀取標准輸入串流時,會使用InputStreamReader,它 繼承了Reader類別,您使用以下的方法來為標准輸入串流建立緩沖區對象:
import java.io.*; public class BuffRdr { public static void main(String[] args) throws IOException{ BufferedReader buf = new BufferedReader( new InputStreamReader(System.in)); System.out.print("Please input a sentence:"); String text = buf.readLine(); System.out.println("Your sentece is:"+text); } }
"new"關鍵詞表示您要建構一個對象為您所用,BufferedReader buf表示宣告一個型態為BufferedReader的對象變量,而new BufferedReader()表示以BufferedReader類別建構一個對象,new InputStreamReader(System.in)表示接受一個System.in對象來建構一個InputStreamReader物件。
readLine()方法會傳回使用者在按下Enter鍵之前的所有字符輸入,不包括最后按下的 Enter返回字符。
4)、標准輸入輸出串流
在之前的HelloWorld程序中,您使用了System類別中的靜態對象out,它提供標准輸出串流(Stream),會在程序開始執行之后自動開啟 並准備接受指定的資料,它通常對應至顯示輸出(Console、終端機輸出)或其它的輸出目的地,它可以被重新導向至一個檔案,您可以在執行程序時使用 '>>'將輸出結果導向至指定的檔案【需要在控制台中執行】,例如:
java HelloWorld >> output.txt
上面的執行會將結果導向至output.txt,而不會在屏幕上顯示"Hello! World!",output.txt中將會有輸出結果"Hello! World"!。
除了標准輸出串流out之外,Java程序在執行之后,還會開啟標准輸入串流in與標准錯誤輸出串流err,下面先說明標准輸入串流in。
標准輸入串流in也是用System類別所提供的靜態對象,在程序開 始之后它會自動開啟,對應至鍵盤或其它的輸入來源,准備接受使用者或其它來源的輸入,您可以使用read()方法來讀取輸入,不過通常很少直接使用它,而 會使用一個Scanner對象為輸入串流作后處理,方法在 取得使用者輸入 簡介過了。
標准錯誤輸出串流err也是在程序執行后自動開啟,它會將指定的字符串 輸出至顯示裝置或其它指定的裝置,與標准輸出串流out不同的是,它會立即顯示指定的(錯誤)訊息給使用者知道,例如即使您指定程序將結果重新導向至文件 案,err輸出串流的訊息並不會被重新導向,而仍會顯示在指定的顯示裝置上。
public class HelloWorld { public static void main(String[] args) { System.out.println("Hello! World!"); System.err.println("Error Message Test"); } }
運行:
java HelloWorld >> output.txt
Error Message Test
開啟output.txt之后,您會發現當中只有"Hello! World!"訊息,而Error Message Test訊息並沒有被導向至檔案中,而是直接顯示在Console(或終端機)中。
要 重新導向標准輸出是用 '>', 標准輸入則是 '<',而 '>>' 除了重導標准輸出之外,還有附加的功能,也就是會把輸出附加到被導向的目標檔案后頭,如果目標檔案本來不存在,那么效果就和 '>' 一樣。(重導——重定向)
5)、Console 輸入格式控制
標准輸出通常是文字模式為主的主控台(終端機),這邊介紹幾個格式控制技巧,在主控台顯示時可以協助輸出的顯示格式。
首先介紹格式字符控制,先表列一些常用的控制字符:
\\ | 反斜線 |
\' | 單引號 ' |
\" | 雙引號 " |
\uxxxx | 以16進位數指定Unicode字符輸出 |
\dxxx | 以8進位數指定Unicode字符輸出 |
\b | 倒退一個字符 |
\f | 換頁 |
\n | 換行 |
\r | 游標移至行首 |
\t | 跳格(一個Tab鍵) |
下面這段程序告訴您如何指定字符編碼來輸出"Hello"這段文字:
System.out.println("\u0048\u0065\u006C\u006C\u006F");
在輸出數值時,預設都會以十進制的方式來顯示數值,下面這幾段程序可以讓您分別以不同進位制來顯示數值:
int x = 19; // 轉成二進制 10011 System.out.println(Integer.toBinaryString(x)); // 轉成十六進制 13 System.out.println(Integer.toHexString(x)); // 轉成八進位 23 System.out.println(Integer.toOctalString(x));
您可以使用 System.out.printf() 作簡單的輸出格式設定,例如:
// 輸出 19 的十進制表示 System.out.printf("%d%n", 19); // 輸出 19 的八進位表示 System.out.printf("%o%n", 19); // 輸出 19 的十六進制表示 System.out.printf("%x%n", 19);
'%d'表示將指定的數值以十進制表示,'%o'是八進位表示,而'%x'是十六進制表示,'%n'是指輸出平台特定的換行字符,如果是在Windows下實際上會置換為 '/r/n',如果是Linux下則會置換為'/n'。
下表簡單列出了一些常用的轉換符號:
%% | 在字符串中顯示% |
%d | 以10進位整數方式輸出,提供的數必須是Byte、 Short、 Integer、Long、或BigInteger |
%f | 將浮點數以10進位方式輸出,提供的數必須是Float、 Double或 BigDecimal |
%e, %E | 將浮點數以10進位方式輸出,並使用科學記號,提供的數必須是 Float、 Double或BigDecimal |
%a, %A | 使用科學記號輸出浮點數,以16進位輸出整數部份,以10進位 輸出指數部份, 提供的數必須是Float、Double、BigDecimal |
%o | 以8進位整數方式輸出,提供的數必須是Byte、Short、 Integer、Long、或BigInteger |
%x, %X | 將浮點數以16進位方式輸出,提供的數必須是Byte、 Short、 Integer、Long、或BigInteger |
%s, %S | 將字符串格式化輸出 |
%c, %C | 以字符方式輸出,提供的數必須是Byte、Short、 Character或 Integer |
%b, %B | 將"true"或"false"輸出(或"TRUE"、 "FALSE",使用 %B)。另外,非null值輸出是"true",null值輸出是"false" |
%t, %T | 輸出日期/時間的前置,詳請看在線API文件 |
您可以在輸出浮點數時指定精度,例如:
System.out.printf("example:%.2f%n", 19.234);
執行結果會輸出:
example:19.23
您也可以指定輸出時,至少要預留的字符寬度,例如:
System.out.printf("example:%6.2f%n", 19.234);
由於預留了6個字符寬度,不足的部份要由空格符補上,所以執行結果會輸出如下(19.23只占五個字符,所以補上一個空白在前端):
example: 19.23
以上只是簡短的列出一些常用的輸出轉換符號,事實上,這些功能都是由 java.util.Formatter 所提供的,如果您需要更多關於輸出格式的控制,您可以看看在線API文件以查詢相關設定。
4、類型
整數
只儲存整數數值,可細分為「短整數」(short)(占2個字節)、整數(int)(占4個字節)與長整數(long)(占8個字節)。
位
Java提供有byte數據型態,專門儲存位數據,例如影像位資 料,一個byte數據型態占一個字節。
浮點數
主要用來儲存小數數值,也可以用來儲存范圍更大的整數,可分為浮點數(float) (占4個字節)與倍精度浮點數(double)(占8個字節)。
字符
用來儲存字符,Java的字符采Unicode編碼,其中前128個 字符編碼與ASCII編碼兼容;每個字符數據型態占兩個字節,可儲存的字符范圍由\ u0000到\uFFFF,由於Java的字符采用Unicode編碼,一個中文字與一個英文字母在Java中同樣都是用一個字符來表示。
布爾數
占內存2個字節,可儲存true與false兩 個數值,分別表示邏輯的「真」與「假」。
使用下面這個程序獲得數值的儲存范圍:
public class DataTypeMaxMin { public static void main(String[] args) { System.out.printf("short range: %d ~ %d\n", Short.MAX_VALUE, Short.MIN_VALUE); System.out.printf("int range: %d ~ %d\n", Integer.MAX_VALUE, Integer.MIN_VALUE); System.out.printf("long range: %d ~ %d\n", Long.MAX_VALUE, Long.MIN_VALUE); System.out.printf("byte range: %d ~ %d\n", Byte.MAX_VALUE, Byte.MIN_VALUE); System.out.printf("float range: %e ~ %e\n", Float.MAX_VALUE, Float.MIN_VALUE); System.out.printf("double range: %e ~ %e\n", Double.MAX_VALUE, Double.MIN_VALUE); } }
其中Byte、Integer、Long、Float、Double都 是java.lang套件下的類別名稱,而 MAX_VALUE與MIN_VALUE則是各類別中所定義的靜態常數成員,分別表示該數據型態可儲 存的數值最大與最小范圍,%e表示用科學記號顯示,執行結果如下所示:
short range: 32767 ~ -32768 int range: 2147483647 ~ -2147483648 long range: 9223372036854775807 ~ -9223372036854775808 byte range: 127 ~ -128 float range: 3.402823e+38 ~ 1.401298e-45 double range: 1.797693e+308 ~ 4.900000e-324
其中浮點數所取得是正數的最大與最小范圍,加上負號即為負數的最大與最小范圍。
5、變量、常量
變量(Variable)是一個指向數據儲存空間的參考,您將數據指定給變量,就是將數據儲存至對應的內存空間,呼叫變量, 就是呼叫對應的內存空間的數據供您使用。
在過去曾流行過匈牙利命名法,也就是在變量名稱前加上變量的數據型態名稱縮寫,例如intNum用來表示這個變量是int整數數據型態,fltNum表示 一個float數據型態,然而隨着現在程序的發展規模越來越大,這種命名方式已經不被鼓勵。
過去的程序在撰寫時,變量名稱的長度會有所限制,但現在已無這種困擾,因而現在比較鼓勵用清楚的名稱來表明變量作用,通常會以小寫字母作為開始,並在每個單字開始時第一個字母使用大寫,例如:
int ageForStudent; int ageForTeacher;
像這樣的名稱可以讓人一眼就看出這個變量的作用,這樣的命名方式,在Java程序設計領域中是最常看到的一種。
變量名稱可以使用底線作為開始,通常使用底線作為開始的變量名稱,表示它是私用的 (Private),只在程序的某個范圍使用,外界並不需要知道有這個變量的存在,通常這樣的變量名稱常用於對象導向程序設計中類別的私有成員(Private member),這樣的命名方式在Java中偶而也 會看到(比較常見於C++的程序撰寫中),一個宣告的例子如下:
double _window_center_x; double _window_center_y;
當您在Java中宣告一個變量,就會配置一塊內存空間給它,這塊空間中原先可能就有數據,也因此變量在宣告后的值是不可預期的,Java對於安全性的要 求極高,您不可以宣告變量后,而在未指定任何值給它之前就使用它,編譯器在編譯時會回報這個錯誤,例如若宣告變量var卻沒有指定值給它,則會顯示以下訊 息:
variable var might not have been initialized
可以的話,盡量在變量宣告后初始其值,您可以使用「指定運算子」 (Assignment operator)=來指定變量的值,例如:
int ageForStudent = 0; double scoreForStudent = 0.0; char levelForStudent = 'A';
上面這段程序在宣告變量的時候,同時指定變量的儲存值,而您也看到如何指定字符給字符變量,字符在指定時需使用引號 ' ' 來包括;在指定浮點數時,會習慣使用小數的方式來指定,如0.0,在Java中寫下0.0這么一個常數的話,其預設為double數據型態。
在宣告變量之后,您可以直接呼叫變量名稱來取得其所儲存的值,下面這個程序是個簡單的示范:
public class UseVar { public static void main(String[] args){ int ageForStudent = 5; double scoreForStudent = 95.0; char lvlForStudent = 'A'; System.out.println("Age\t Score\t Rank"); System.out.printf("%3d\t %5.1f\t %4c", ageForStudent, scoreForStudent, lvlForStudent); } }
在Java中寫下一個數值,稱之為字面常量(Literal constant), 它會存在內存的某個位置,您無法改變它的值;而在使用變量的時候,也會使用一種叫「常數」的變量,嚴格來說它並不是常數,只不過指定數值給這個變量之 后,就不可再改變其值,有人為了區分其與常數的差別,還給了它一個奇怪的名稱:「常數變數」。
先不要管「常數變量」這個怪怪的名稱,其實它終究是個變量而已,只是在宣告變量名稱的同時,加上"final"來限定,只不過這個變量一但指定了值,就不可以再改變它的值,如 果程序中有其它程序代碼試圖改變這個變量,編譯器會先檢查出這個錯誤,例如:
final int maxNum = 10; maxNum =20;
這一段程序代碼中的maxNum變量使用final來限定,所以它在指定為10之后,就不可以再指定值給它,所以第二次指定會被編譯器指出錯誤:
cannot assign a value to final variable maxNum
使用final來限定的變量,目的通常就是不希望其它的程序代碼來變動它的值,例如用於循環計數次數的指定(循環之后就會學到),或是像圓周率PI的指定。
6、類型轉換
Java對於程序的安全性要求極高,型態轉換在某些情況一定要明確指定,就是在使用指定運算子時,將精確度大的指定給精確度小的變量時,由於在精確度上會 有遺失的現象,編譯器會認定這是一個錯誤,例如:
int testInteger = 0; double testDouble = 3.14; testInteger = testDouble; System.out.println(testInteger);
這段程序在編譯時會出現以下的錯誤訊息:
possible loss of precision found?: double required: int testInteger = testDouble ^ 1 error
如果您確定這是您要的結果,您必須明確加上轉換的限定字:
testInteger = (int) testDouble;
7、比較、條件運算符
使用 == 運算時要注意的是,對於對象來說,兩個對象參考之間使用 == 作比較時,是比較其名稱是否參考至同一對象,而不是比較其內容。
Java中的「條件運算子」 (Conditional operator),它的使用方式如下:
條件式 ? 成立傳回值 : 失敗傳回值
import java.util.Scanner; public class UseConditionOperator { public static void main(String[] args){ Scanner scanner = new Scanner(System.in); System.out.print("Input a number:"); int inputedNumber = scanner.nextInt(); System.out.println(inputedNumber + "是不是奇數?" + (inputedNumber%2 == 0 ? '否' : '是')); } }
8、邏輯、位運算
ps:原網站上說的補碼符~,實際是反碼。
顯示各個運算的結果:
public class BitwiseOperator { public static void main(String[] args) { System.out.println("AND運算:"); System.out.println("0 AND 0\t\t" + (0 & 0)); System.out.println("0 AND 1\t\t" + (0 & 1)); System.out.println("1 AND 0\t\t" + (1 & 0)); System.out.println("1 AND 1\t\t" + (1 & 1)); System.out.println("\nOR運算:"); System.out.println("0 OR 0\t\t" + (0 | 0)); System.out.println("0 OR 1\t\t" + (0 | 1)); System.out.println("1 OR 0\t\t" + (1 | 0)); System.out.println("1 OR 1\t\t" + (1 | 1)); System.out.println("\nXOR運算:"); System.out.println("0 XOR 0\t\t" + (0 ^ 0)); System.out.println("0 XOR 1\t\t" + (0 ^ 1)); System.out.println("1 XOR 0\t\t" + (1 ^ 0)); System.out.println("1 XOR 1\t\t" + (1 ^ 1)); } }
9、遞增、遞減運算符【略】
10、if語句【略】
11、switch語句【略】
12、for語句【略】
13、while,do-while語句【略】
14、break、continue語句【略】
15、裝箱boxing、拆箱unboxing
自動裝箱運用的方法如下:
int i = 10; Integer integer = i;
也可以使用更一般化的Number,例如:
Number number = 3.14f;
3.14f會先被自動裝箱為Float,然后指定給number。
J2SE 5.0中可以自動裝箱,也可以自動拆箱(Unboxing),例如下面這樣寫是可以的:
Integer i = 10; System.out.println(i + 10); System.out.println(i++);
上例中會顯示20與10,編譯器會自動幫您進行自動裝箱與拆箱,即10會先被裝箱,然后在i + 10時會先拆箱,進行加法運算;i++該行也是先拆箱再進行遞增運算。再來看一個例子:
Boolean boo = true; System.out.println(boo && false);
同樣的,先將boo拆箱,再與false進行AND運算,結果會顯示false。
16、一維數組
在Java中可以這么宣告一個數組並初始數組內容:
int[] score = {90, 85, 55, 94, 77};
上面這個程序片段宣告了一個score數組,它的內容包括90、85、55、94與77這五個元素,我們要存取數組時,必須使用索引來指定存取數組中的哪個元素,在Java中,數組的索引是由0開始,也就是說索引0的位置儲存90、索引1的位置儲存85、索引2的位置儲存55,依此類推。
下面這個程序可以讓您使用for循環來取出數組中的元素值:
public class ScoreArray { public static void main(String[] args) { int[] score = {90, 85, 55, 94, 77}; for(int i = 0; i < score.length; i++) System.out.print(score[i] + " "); System.out.println(); } }
在這個程序中,您使用了length這個屬性成員,這邊涉及到一個觀念,在Java中,數組是一個對象,而不是單純的數據集合,當您宣告一個數組時,其實就是在配置一個數組對象,上面的程序只是數組宣告與初始化成員的一個簡易宣告方式,數組對象的length屬性成員可以取回這個數組的長度,也就是元素個數。
其實在Java中,對象都是以new來配置內存空間,數組的使用也不例外,一個完整的數組宣告方式如下所示:
int[] arr = new int[10];
在上面的宣告中,會為arr配置10個整數的數組元素,索引為0到9,初始值預設為0,在Java中配置數組之后,若還沒有指定初值,則依數據型態的不同,會預設有不同的初值,如下所示:
數據型態 | 初值 |
byte | 0 |
short | 0 |
int | 0 |
long | 0L |
float | 0.0f |
double | 0.0d |
char | \u0000 |
boolean | false |
由於數組的內存空間是使用new配置而來,這意味着您也可以使用動態的方式來宣告數組長度,而不用在程序中事先決定數組大小,例如:
import java.util.Scanner; public class DynamicAnnounceArray { public static void main(String[] args){ Scanner scanner = new Scanner(System.in); System.out.print("Input the size of Array:"); int length = scanner.nextInt(); int[] arr = new int[length]; System.out.println("The contents of Array:"); for(int i=0;i<arr.length;i++){ System.out.printf("%3d",arr[i]); } } }
17、二維數組、不規則數組
public class TwoDimArray { public static void main(String[] args){ int[][] arr = {{1,2,3}, {4,5,6}}; for (int i = 0; i<arr.length; i++ ){ for (int j = 0; j<arr[0].length; j++){ System.out.printf("%3d",arr[i][j]); } System.out.println(" "); } } }
在使用二維數組對象時,注意length所代表的長度,數組名直接加上length,所指的是有幾列(Row),而使用索引加上length,指的是該列所擁有的元素,也就是行(Column)數目,要了解為何是這樣,必須從對象配置的角度來說明。
以對象的方式來配置一個二維數組對象,您使用以下的語法:
int[][] arr = new int[2][3];
在上面的程序片段中,其實arr[0]、arr[1]是兩個一維數組對象,其長度為3,而arr的型態為int[][],內容值為arr[0]與arr[1],其關系如下:
從上圖中您可以看到,arr名稱參考至int[][]型態的對象,而arr[0]與arr[1]再分別參考至一個一維數組對象,所以這也就是為何上面的程序中,使 用的length所表示的長度意義,您也可以知道,在Java中,一個二維數組的配置,各個元素的內存位置並不是連續的。
同樣的道理,您也可以宣告三維以上的數組,如果要宣告同時初始元素值,可以使用以下的語法:
int[][][] arr = { {{1, 2, 3}, {4, 5, 6}}, {{7, 8, 9}, {10, 11, 12}} };
不規則數組。數組的維度不一定要是四四方方的,您也可以制作一個二維數組,而每個維度的長度並不相同,例如:
public class TwoDimArray { public static void main(String[] args) { int arr[][]; arr = new int[2][]; arr[0] = new int[3]; arr[1] = new int[5]; for(int i = 0; i < arr.length; i++) { for(int j = 0; j < arr[i].length; j++) arr[i][j] = j + 1; } for(int i = 0; i < arr.length; i++) { for(int j = 0; j < arr[i].length; j++) System.out.print(arr[i][j] + " "); System.out.println(); } } }
18、慎用boxing
自動裝箱與拆箱是編譯器在編譯時期為您作好一切的事情,是編譯蜜糖(Compiler sugar),這很方便,但在運行階段您還是了解Java的語義,例如下面的程序是可以通過編譯的:
Integer i = null; int j = i;
語法是在編譯時期是合法的,但是在運行時期會有錯誤,因為null表示 i 沒有參考至任何的對象實體,它可以合法的指定給對象參考名稱,但null值對於基本型態 j 的指定是不合法的,上面的寫法在運行時會出現NullPointerException的錯誤。
再來看一個,先看看程序,您以為結果是如何?
Integer i1 = 100; Integer i2 = 100; if (i1 == i2) System.out.println("i1 == i2"); else System.out.println("i1 != i2");
以自動裝箱與拆箱的機制來看,我想您會覺得結果是顯示"i1 == i2",您是對的!那么下面這個您覺得結果是什么?
Integer i1 = 200; Integer i2 = 200; if (i1 == i2) System.out.println("i1 == i2"); else System.out.println("i1 != i2");
結果是顯示"i1 != i2",這有些令人訝異,語法完全一樣,只不過改個數值而已,結果卻相反。
其實這與'=='運算子的比較有關,'=='可用來比較兩個基本型態的變量值是否相等,事實上'=='也用於判斷兩個對象變量名稱是否參考至同一個對象。
所以'=='可以比較兩個基本型態的變量值是否相等,也可以判斷兩個對象變量的參考對象是否相同,當您如前兩個程序的方式撰寫時,編譯器不知道您實際上要比較的是哪一種?所以對於值從-128到127之間的值,它們被裝箱為Integer對象后,會存在內存之中被重用,所以當值在100,使用'=='進行比較時,i1 與 i2實際上參考至同一個對象。
如果超過了從-128到127之間的值,被裝箱后的Integer對象並不會被重用,即相當於每次都新建一個Integer對象,所以當值在 200,使用'=='進行比較時,i1與i2參考的是不同的對象。
所以不要過份依賴自動裝箱與拆箱,您還是必須知道基本型態與對象的差異,上面的程序最好還是依正規的方式來寫,而不是依賴編譯蜜糖(Compiler sugar),例如當值為200時,必須改寫為以下才是正確的。
Integer i1 = 200; Integer i2 = 200; if (i1.equals(i2)) System.out.println("i1 == i2"); else System.out.println("i1 != i2");
結果這次是顯示"i1 == i2"了,使用這樣的寫法,相信您也會比較放心一些,總之一個原則:如果您不確定就不要用。
19、數組復制
可以使用System.arraycopy()方法來進行數組復制,這個方式必須明確自行新建立一個數組對象。在JDK 6中,Arrays 類別 新增了copyOf()方法,可以直接傳回一個新的數組對象,而當中包括復制的內容,例如:
import java.util.Arrays; public class ArrayCopy { public static void main(String[] args){ int[] arr1 = {1,2,3,4,5}; int[] arr2 = new int[5]; int[] arr3 = Arrays.copyOf(arr1,arr1.length); System.arraycopy(arr1, 0, arr2, 0, arr1.length); for (int i=0; i<arr1.length; i++){ System.out.printf("%3d", arr1[i]); } System.out.println(" "); for (int j=0; j<arr2.length; j++){ System.out.printf("%3d",arr2[j]); } System.out.println(" "); for (int j=0; j<arr3.length; j++){ System.out.printf("%3d",arr3[j]); } } }
Arrays的copyOf()方法傳回的數組是新的數組對象,所以您改變傳回數組中的元素值,也不會影響原來的數組。
copyOf()的第二個自變量指定要建立的新數組長度,如果新數組的長度超過原數組的長度,則保留數組默認值(超出部分為0、0L...)。
20、Arrarys類
數組中基本操作的排序、搜尋與比較等動作是很常見的,在Java中提供了Arrays類別可以協助您作這幾個動作,Arrays類別位於java.util套件中,它提供了幾個靜態方法可以直接呼叫使用。
sort() |
這個方法可以幫助您對指定的數組排序,所使用的是快速排序法 |
binarySearch() |
這個方法可以讓您對已排序的數組進行二元搜尋,如果找到指定的值就傳回該值所 在的索引,否則就么回負值 |
fill() |
當我們配置一個數組之后,其會依數據型態來給定默認值,例如整數數組就初始為 0,您可以使用Arrays.fill()方法來將所有的元素設定為指定的值 |
equals() |
比較兩個數組中的元素值是否全部相等,如果是將傳回true,否則傳回 false |
下面這個程序示范數組的排序與搜尋:
import java.util.Arrays; import java.util.Scanner; public class UseArrays { public static void main(String[] args){ Scanner scanner = new Scanner(System.in); int[] arr = {93, 5, 3, 55, 57, 7, 2, 73, 41, 91}; System.out.print("排序前:"); for (int i=0; i<arr.length; i++){ System.out.print(arr[i]+" "); } System.out.println(); Arrays.sort(arr); System.out.print("排序后:"); for (int i=0; i<arr.length; i++){ System.out.print(arr[i]+" "); } System.out.println(); System.out.print("輸入搜索值:"); int key = scanner.nextInt(); int find = -1; if((find = Arrays.binarySearch(arr,key))>-1){ System.out.println("找到值於索引"+find+"位置。"); }else{ System.out.println("找不到指定值。"); } } }
下面這個程序示范數組的填充與比較:
import java.util.Arrays; public class UseArrays2 { public static void main(String[] args){ int[] arr1 = new int[10]; int[] arr2 = new int[10]; int[] arr3 = new int[10]; Arrays.fill(arr1, 5); Arrays.fill(arr2, 5); Arrays.fill(arr3, 10); System.out.print("arr1 :"); for (int i=0; i<arr1.length; i++){ System.out.print(arr1[i]+" "); } System.out.println("\narr1 = arr2 ? " + Arrays.equals(arr1, arr2)); System.out.println("arr1 = arr3 ? " + Arrays.equals(arr1, arr3)); } }
請注意到,您不可以用==來比較兩個數組的元素值是否相等,==使用於對象比對時,是用來比對兩個對象名稱是否參考至同一個對象,下面這個程序是個簡單的 示范:
public class TestEqual { public static void main(String[] args){ int[] arr1 = new int[5]; int[] arr2 = new int[5]; int[] tmp = arr1; System.out.println(arr1 == tmp); System.out.println(arr2 == tmp); } }
事實上,J2SE 5.0 對Arrays類別作了不少的修改與新增,由此可見數組操作在程序中的重要性,這邊介紹Arrays新增的兩個方法:deepEquals()與deepToString()。
deepEquals() |
對數組作深層比較,簡單的說,您可以對二維仍至三維以上的數組進行比較是否相 等 |
deepToString() |
將數組值作深層輸出,簡單的說,您可以對二維仍至三維以上的數組輸出其字符串值 |
直接來看個程序比較清楚:
import java.util.Arrays; public class UseArrays { public static void main(String args[]) { int[][] arr1 = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}}; int[][] arr2 = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}}; int[][] arr3 = {{0, 1, 3}, {4, 6, 4}, {7, 8, 9}}; System.out.println("arr1 equals arr2? " + Arrays.deepEquals(arr1, arr2)); System.out.println("arr1 equals arr3? " + Arrays.deepEquals(arr1, arr3)); System.out.println("arr1 deepToString()\n\t" + Arrays.deepToString(arr1)); } }
執行結果:
arr1 equals arr2? true arr1 equals arr3? false arr1 deepToString() [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
如果您有數組操作方面的相關需求,可以先查查 java.util.Arrays 的API文件。
21、進階的數組觀念
藉由對數組對象的進一步探討,您可以稍微了解Java對對象處理的一些作法,首先來看看一個一維數組參考名稱的宣告:
int[] arr;
在這個宣告中,arr表示一個可以參考至一維數組對象的名稱,但是目前還沒有指定它參考至任何的對象,在Java中,=運算子用於基本數據型態時,是 將值復制給變量,但當它用於對象時,則是將對象指定給參考名稱來參考,您也可以將同一個對象指定給兩個參考名稱,當對象的值藉由其中一個參考名稱變更 時,另一個參考名稱所參考到的值也會更動。
在宣告int[] arr之后,arr表示它是一個一維數組對象的參考名稱,所以它可以參考至任何長度的一維數組對象。
您了解到在Java中數組是一個對象,而使用 = 指定時是將對象指定給數組名來參考,而不是數組復制,如果您想將整個數組的值復制給另一個數組該如何作呢?您可以使用循環,將整個數組的元素值走訪一遍,並指定給另一個數組相對應的索引位置,例如下面這個例子:
public class AdvancedArray { public static void main(String[] args) { int[] arr1 = {1, 2, 3, 4, 5}; int[] arr2 = new int[5]; for(int i = 0; i < arr1.length; i++) arr2[i] = arr1[i]; for(int i = 0; i < arr2.length; i++) System.out.print(arr2[i] + " "); System.out.println(); } }
另一個更簡便的方法是使用System類別所提供的靜態方法arraycopy(),其語法如下:
System.arraycopy(來源, 起始索引, 目的, 起始索引, 復制長度);
22、對象數組
就如同Java的基本數據型態可以宣告為數組,Java中的對象也可以使用數組來加以管理,但兩者的索引存取意義有些不同。
使用Java的基本數據型態來宣告數組,每一個數組的索引位置都可以儲存一個數值,這不用懷疑,但是如果是對象數組,其每一個索引位置是用來參考至一個對象,例如宣告一個字符串對象數組:
String[] names = {"caterpillar", "momor", "beckyday","bush"};
在這個例子中,names中的每個索引位置其真正意義為參考,如下所示:
names[0] => 參考至"caterpillar"對象
names[1] => 參考至"momor"對象
names[2] => 參考至"beckyday"對象
names[3] => 參考至"bush"對象
如果您作了下面的指定:
names[1] = names[2];
則索引位置的參考會變成如下:
names[0] => 參考至"caterpillar"對象
names[1] => 都參考至"beckyday"對象 <= names[2]
names[3] => 參考至"bush"對象
如果是基本數據型態的話,就不是如此,例如:
int arr[] = {1, 2, 3, 4}; arr[1] =arr[2];
經過以上的宣告與指定后,arr[2]的值會復制給arr[1],也就是說它們兩個擁有各自的值,雖然arr[1]的值等於3,arr[2]的值也等於 3,但儲存在不同的內存位置,彼此不相互干擾。
23、foreach語句
foreach的語法,它可以應用於數組的循序存取上,其語法如下:
for(type element : array) { System.out.println(element).... }
例如循序存取一個數組的方式如下:
int[] arr = {1, 2, 3, 4, 5}; for(int element : arr) System.out.println(element);
注意:element的宣告必須在for()之中,例如以下就無法通過編譯:
int[] arr = {1, 2, 3, 4, 5}; int element = 0; for(element : arr) System.out.println(element);
那么二維數組呢?基本上您要是了解數組本身就是個對象,您自然就會知道如何存取,舉個例子:
int[][] arr = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}}; for(int[] row : arr) for(int element : row) System.out.println(element);
24、String類
由字符所組成的一串文字符號,稱之為字符串,在Java中字符串可以使用String類別來建構,例如您可以使用以下的方式來宣告並初始一個字符串變量:
String text = "字符串的使用";
注意字符串的直接指定必須使用"",而字符串是使用Unicode字符來建構,在建構一個字符串對象變量之后,您可以直接在輸出串流中指定變量名稱來輸出字符串,例如:
System.out.println(text);
字符串的串接在Java中可以直接使用 + 運算子,+ 本來是加法運算子,而它被重載(Override)為可以直接用於字符串的串接,例如您可以這么使用字符串:
String msg = "哈啰!"; msg = msg + "Java Programming!"; System.out.println(msg);
這一段程序代碼會在主控台上顯示 "哈啰!Java Programming!"。
用於生成字符串對象的String類別擁有幾個操作字符串的方法,在這邊先介紹幾個常用的:
length() |
取得字符串的字符長度 |
equals() |
判斷原字符串中的字符是否相等於指定字符串中的字符 |
toLowerCase() |
轉換字符串中的英文字符為小寫 |
toUpperCase() |
轉換字符串中的英文字符為大寫 |
下面這個程序介紹以上的幾個操作字符串方法的使用與結果:
public class UseString { public static void main(String[] args){ String text = "Hello"; System.out.println("字符串內容:"+text); System.out.println("字符串長度:"+text.length()); System.out.println("等於Hello?" + text.equals("Hello")); System.out.println("轉換為大寫:"+text.toUpperCase()); System.out.println("轉換為小寫:"+text.toLowerCase()); } }
如果您要將輸入的字符串轉換為整數、浮點數等等數據型態,您可以使用以下各類別所提供的各個靜態剖析方法:
Byte.parseByte(字符串) |
將字符串剖析為位 |
Short.parseShort(字符串) |
將字符串剖析為short整數 |
Integer.parseInt(字符串) |
將字符串剖析為integer整數 |
Long.parseLong(字符串) |
將字符串剖析為long整數 |
Float.parseFloat(字符串) |
將字符串剖析為float浮點數 |
Double.parseDouble(字符串) |
將字符串剖析為double浮點數 |
注意如果指定的字符串無法剖析為指定的數據型態數值,則會發生NumberFormatException例外。
之前宣告字符串時,都是以這樣的樣子來宣告:
String str = "caterpillar";
這樣的宣告方式看來像是基本數據型態宣告,但事實上String並不是Java的基本數據型態,String是java.lang套件下所提供的類別,如果以配置對象的觀念來宣告字符串,應該是這樣的:
String str = new String("caterpillar");
不過事實上它與下面這段是有差別的:
String str = "caterpillar";
一個字符串其實是由字符數組所組成,所以使用String類別宣告字符串后,該字符串會具有數組索引的性質,以下介紹幾個與索引相關的方法:
char charAt(int index) |
傳回指定索引處的字符 |
int indexOf(int ch) |
傳回指定字符第一個找到的索引位置 |
int indexOf(String str) |
傳回指定字符串第一個找到的索引位置 |
int lastIndexOf(int ch) |
傳回指定字符最后一個找到的索引位置 |
String substring(int beginIndex) |
取出指定索引處至字符串尾端的子字符串 |
String substring(int beginIndex, int endIndex) |
取出指定索引范圍子字符串 |
char[] toCharArray() |
將字符串轉換為字符Array |
下面這個程序是個簡單的示范:
public class UseString { public static void main(String[] args) { String text = "Your left brain has nothing right.\n" + "Your right brain has nothing left.\n"; System.out.println("字符串內容: "); for(int i = 0; i < text.length(); i++) System.out.print(text.charAt(i)); System.out.println("\n第一個left: " + text.indexOf("left")); System.out.println("最后一個left: " + text.lastIndexOf("left")); char[] charArr = text.toCharArray(); System.out.println("\n字符Array內容: "); for(int i = 0; i < charArr.length; i++) System.out.print(charArr[i]); } }
在建構字符串對象時,除了直接指定字符串常數之外,您也可以使用字符數組來建構,例如:
char[] name = {'c', 'a', 't', 'e', 'r', 'p', 'i', 'l', 'l', 'a', 'r'}; String str = new String(name);
上面這個程序片段使用字符數組name,建構出一個內容為"caterpillar"的字符串。
除了以上所介紹的幾個方法之外,您應該查查API手冊,了解更多有關於String類別的方法,例如我們可以使用endsWith()方法來過濾文件名稱,下面這個程序過濾出檔案類型為jpg的檔案:
public class UseString { public static void main(String[] args) { String[] filenames = {"caterpillar.jpg", "cater.gif", "bush.jpg", "wuwu.jpg", "clockman.gif"}; System.out.print("過濾出jpg檔案: "); for(int i = 0; i < filenames.length; i++) if(filenames[i].endsWith("jpg")) System.out.print(filenames[i] + " "); System.out.println(""); } }
25、不可變的(immutable)字符串
一個字符串對象一旦被配置,它的內容就是固定不可變的(immutable),例如下面這個宣告:
String str = "caterpillar";
這個宣告會配置一個長度為11的字符串對象,您無法改變它的內容;別以為下面這個宣告就是改變一個字符串對象的內容:
String str = "just";
str = "justin";
事實上,在這個程序片段中,會有兩個字符串對象,一個是"just",長度為4,一個是"justin",長度為6,它們兩個是不同的字符串對象,您並不是在 "just"字符串后加上"in"字符串,而是讓str名稱參考至新的字符串對象,如下所示:
原來參考至此
str --------> "just"
重新指定后
"just" 等待回收
str ---------> "justin"
參考新的字符串對象
在Java中,使用 = 將一個字符串對象指定給一個名稱,其意義為改變名稱的參考對象,原來的字符串對象若沒有其它名稱來參考它,就會在適當的時機被Java的「垃圾回收」(Garbage collection)機制回收,在Java中,程序設計人員通常不用關心無用對象的資源釋放問題,Java會檢查對象是否不再被參考,如果沒有任何名稱參考的對象將會被回收。
如果您在程序中使用下面的方式來宣告,則實際上是指向同一個字符串對象:
String str1 = "flyweight"; String str2 = "flyweight"; System.out.println(str1 == str2);
程序的執行結果會顯示true,在Java中,會維護一個String Pool,對於一些可以共享的字符串對象,會先在String Pool中查找是否存在相同的String內容(字符相同),如果有就直接傳回,而不是直接創造一個新的String對象,以減少內存的耗用。
再來個一看例子,String的intern()方法,來看看它的API說明的節錄:
Returns a canonical representation for the string object.
A pool of strings, initially empty, is maintained privately by the class String.
When the intern method is invoked, if the pool already contains a string equal to this String object as determined by the equals(Object) method, then the string from the pool is returned. Otherwise, this String object is added to the pool and a reference to this String object is returned.
這段話其實說明了 Flyweight 模式 的運作方式,來用個實例來說明會更清楚:
public class StringIntern { public static void main(String[] args) { String str1 = "fly"; String str2 = "weight"; String str3 = "flyweight"; String str4; str4 = str1 + str2; System.out.println(str3 == str4); str4 = (str1 + str2).intern(); System.out.println(str3 == str4); } }
在程序中第一次比較str3與str4對象是否為同一對象時,您知道結果會是false,而intern()方法會先檢查 String Pool中是否存在字符部份相同的字符串對象,如果有的話就傳回,由於程序中之前已經有"flyweight"字符串對象,intern()在String Pool中發現了它,所以直接傳回,這時再進行比較,str3與str4所指向的其實是同一對象,所以結果會是true。
注意到了嗎?== 運算在Java中被用來比較兩個名稱是否參考至同一對象,所以不可以用==來比較兩個字符串的內容是否相同,例如:
String str1 = new String("caterpillar"); String str2 = new String("caterpillar"); System.out.println(str1 == str2);
上面會顯示false的結果,因為str1與str2是分別參考至不同的字符串對象,如果要比較兩個(字符串)對象是否相同,您要使用equals()方法,例如:
String str1 = new String("caterpillar"); String str2 = new String("caterpillar"); System.out.println(str1.equals(str2));
26、分離字符串
將字符串依所設定的條件予以分離是很常見的操作,例如指令的分離,文本文件的數據讀出等等,以后者而言,當您在文本文件中儲存以下的資料時,在讀入檔案后,將可以使用String的split()來協助每一格的資料分離。
假設在文本文件中有如下的內容,每筆數據中是以tab分開:
cater 64/5/26 093900230 25433343
momor 67/3/26 0939123456 5434233
下面這個程序是一個簡單的范例,假設String對象的數據就是檔案中的一行文字數據:
public class StringSplit { public static void main(String[] args){ String strOfReaded1 = "cater\t64/5/26\t0939002302\t5433343"; String[] tokens = strOfReaded1.split("\t"); for (String token : tokens){ System.out.print(token+"<token>"); } System.out.println(); } }
執行結果:
cater<token>64/5/26<token>0939002302<token>5433343<token>
split()依您所設定的分隔設定,將字符串分為數個子字符串並以String數組傳回。
27、使用正則表達式(Regular expression)
正則表示式最早是由數學家Stephen Kleene於1956年提出,主要使用在字符字符串的格式比對,后來在信息領域廣為應用,現在已經成為ISO(國際標准組織)的標准之一。
您可以在API文件的 java.util.regex.Pattern 類別中找到支持的正則表示式相關信息。
如果您使用String類別來配置字符串對象,您可以使用簡易的方法來使用正則表示式,並應用於字符串的比對或取代等動作上,以下先介紹幾個簡單的正則表示式。
例如一些常用的范圍,我們可以使用預先定義的字符類別:
. |
符合任一字符 |
\d |
等於 [0-9] 數字 |
\D |
等於 [^0-9] 非數字 |
\s |
等於 [ \t\n\x0B\f\r] 空格符 |
\S |
等於 [^ \t\n\x0B\f\r] 非空格符 |
\w |
等於 [a-zA-Z_0-9] 數字或是英文字 |
\W |
等於 [^a-zA-Z_0-9] 非數字與英文字 |
符合任一字符。例如有一字符串abcdebcadxbc,使用.bc來比對的話,符合的子字符串有abc、ebc、xbc三個;如果使用..cd,則符合的子字符串只有abcd。
以上的例子來根據字符比對,您也可以使用「字符類」(Character class)來比較一組字符范圍,例如:
[abc] |
a、b或c |
[^abc] |
非a、b、c的其它字符 |
[a-zA-Z] |
a到z或A到Z(范圍) |
[a-d[m-p]] |
a到d或m到p(聯集) |
[a-z&&[def]] |
d、e或f(交集) |
[a-z&&[^bc]] |
a到z,除了b與c之外(減集) |
[a-z&&[^m-p]] |
a到z且沒有m到p(a-lq-z)(減集) |
一次只指定一個字符不過癮,也可以用Greedy quantifiers來指定字符可能出現的次數:
X? |
X出現一次或完全沒有 |
X* |
X出現零次或多次 |
X+ |
X出現一次或多次 |
X{n} |
X出現n次 |
X{n,} |
X出現至少n次 |
X{n,m} |
X出現至少n次,但不超過m次 |
另外還有Reluctant quantifiers、Possessive quantifiers等的指定,您可以自行參考 java.util.regex.Pattern 類別中的說明。
在String類別中,
matches()方法可以讓您驗證字符串是否符合指定的正規表示式,這通常用於驗證使用者輸入的字符串數據是否正確,例如 電話號碼格式;
replaceAll()方法可以將符合正規表示式的子字符串置換為指定的字符串;
split()方法可以讓您依指定的正規表示式,將符合的子 字符串分離出來,並以字符串數組傳回。
import java.util.Scanner; public class UseRegularExpression { public static void print(String str){ System.out.print(str); } public static void println(String str){ System.out.println(str); } public static void main(String[] args){ Scanner scanner = new Scanner(System.in); String str = "abcdefgabcabc"; println(str.replaceAll(".bc", "###")); print("請輸入手機號:"); str = scanner.next(); //簡單格式驗證 if(str.matches("[0-9]{4}-[0-9]{6}")){ println("格式正確。"); }else{ println("格式錯誤。"); } print("輸入href標簽:"); //Scanner.next()以空白區隔 str = scanner.next()+" "+scanner.next(); //驗證href標簽 if(str.matches("<a.+href*=*['\"]?.*?['\"]?.*?>")){ println("格式正確。"); }else{ println("格式錯誤。"); } print("輸入電子郵件:"); str = scanner.next(); //驗證電郵格式 if(str.matches("^[_a-z0-9-]+([.][_a-z0-9-]+)*@[a-z0-9-]+([.][a-z0-9-]+)*$")){ println("格式正確"); }else{ println("格式錯誤"); } } }
28、Pattern、Matcher
String上的正則表示式,實際上是利用了Pattern與Matcher的功能,當您呼叫String的matches()方法時,實際上是呼叫Pattern的靜態(static)方法matches(),這個方法會傳回boolean值,表示字符串是否符合正則表示式。
如果您想要重復使用您的正則表示式,則您可以使用Pattern的靜態方法compile()進行編譯,它會傳回一個Pattern的實例,代表您的正則表示式,之后您就可以重復使用這個實例的matcher()方法來進行字符串比對,這個方法會傳回一個Matcher的實例, Matcher上有一些尋找符合正則式條件的方法可供操作。
import java.util.regex.*; public class UsePatternMather { public static void main(String[] args){ String phones1 = "Justin's phone number: 0939-100391\n" + "momor's phone number: 0939-666888\n"; Pattern pattern = Pattern.compile(".*0939-\\d{6}"); Matcher matcher = pattern.matcher(phones1); while(matcher.find()){ System.out.println(matcher.group()); } String phones2 = "caterpillar's phone number: 0952-600391\n" + "bush's phone number: 0939-550391"; matcher = pattern.matcher(phones2); while(matcher.find()){ System.out.println(matcher.group()); } } }
這個程序會尋找手機號碼為0939開頭的號碼,假設您的號碼來源不只一個(如phones1、phones2),我們可以編譯好正則表示式並傳回一個 Pattern對象,之后就可以重復使用。
29、StringBuilder 類別
一個String對象的長度是固定的,您不能改變它的內容,或者是附加新的字符至String對象中。
在 J2SE 5.0 提供StringBuilder類別,使用這個類別所產生的對象預設會有16個字符的長度,您也可以自行指定初始長度,如果附加至對象的字符超出可容納的長度,則StringBuilder對象會自動增加長度。
在StringBuilder中,length()可傳回目前對象中的字符長度,而capacity()可傳回該對象目前可容納的字符容量,下面這個程序是個簡單的示范:
public class UseStringBuilder { public static void main(String[] args) { StringBuilder strBuilder = new StringBuilder("Knowledge is power!"); System.out.println("內容: " + strBuilder); System.out.println("長度: " + strBuilder.length()); System.out.println("容量: " + strBuilder.capacity()); } }
執行結果:
內容: Knowledge is power! 長度: 19 容量: 35
StringBuilder擁有幾個操作字符串的方法,例如insert()方法可以將字符插入指定的位置,如果該位置以后有字符,則將所有的字符往后移,deleteChar()方法可以刪除指定位置的字符,而reserve()方法可以反轉字符串,詳細的使用可以查詢看看 java.lang.StringBuilder 的API說明。
您可能會問 java.lang.StringBuffer 呢?事實上,StringBuilder被設計為與StringBuffer相同的操作接口,但不考慮多執行緒下同步的問題,所以在單執行緒下,您可以將以前使用StringBuffer撰寫的程序,通通換為StringBuilder而仍可以運作,並可以獲得較好的效能;如果您的程序是在多執行緒下操作,則可以使用StringBuffer,讓這個類別自行管理同步問題。【執行緒——線程】
30、命令行參數(Command line argument)
在使用主控台啟動一個Java程序時,我們可以一並指定一些參數,以讓程序進行相對應的功能,例如:
$java 類別名稱 -compare a.java b.java
像這樣的功能,您可以使用命令列自變量(Command line argument)來達到,在我們撰寫主程序時,會在自變量列撰寫String[] args,它就是用來接受一個自變量指定的字符串數組,您只要使用索引取出args中的元素值,就可以取出程序運行時的參數,下面這個程序是個簡單的示范:
public class CommandLineArg { public static void main(String[] args) { System.out.print("讀入的自變量: "); for(int i = 0; i < args.length; i++) System.out.print(args[i] + " "); System.out.println(""); } }
執行結果:
$ java CommandLineArg -d /mnt/win_d/sample/
讀入的自變量: -d /mnt/win_d/sample/
args索引0的值是從程序名稱后第一個自變量開始,以空白為區隔依序儲存在args數組中,當然,您可以使用 J2SE 5.0 的foreach來改寫上面的程序:
public class CommandLineArg { public static void main(String[] args) { System.out.print("讀入的自變量: "); for(String arg : args) System.out.print(arg + " "); System.out.println(); } }
接下來介紹一些處理命令列自變量的技巧,由於命令列自變量是儲存在數組中,取出這些自變量的最好方式當然就是使用for循環,而我們通常使用一個前導字符, 例如'-'來指定自變量的選項功能,由於arg是個字符數組,自然的您可以使用switch來比對前導字符,例如:
for(String arg : args) { switch(arg.charAt(0)) { case '-': // 處理參數,執行選項,例如-o、-p、-r等等 default: // 執行對應功能 } }
在判斷執行選項的case中,您可以進一步檢查第二個字符,例如:
switch(arg.charAt(1)) { case 'o': // 選項o的處理 break; case 'p': // 選項p的處理 break; case 'r': // 選項r的處理 break; default: // 選項錯誤處理或其它處理 }
以上是命令列自變量處理時的大致流程,當然不同的程序會有不同的處理方式,不過大致上不離以上的架構。
31、使用Class類型【快速瀏覽】
對象導向設計中,對象並不是憑空產生的,您必須先定義您的對象,您要一個規格書,這個規格書稱之為類別(Class)。
在Java中使用"class"關鍵詞來書寫類別(規格書),您使用類別來定義一個對象(object)時,您考慮這個對象可能擁有的「屬性」(Property,在Java中則是用Field)與「方法」(Method)。屬性是參與對象內部運算的數據成員,而方法則是對象與外界互動的動態操作。
您使用類別定義出對象的規格書,之后根據這個規格書來建構對象,然后透過對象所提供的操作接口來與程序互動。
舉個例子來說,您可以定義一個對象:「球」。
考慮球有各種不同的顏色(或名稱),以及球最基本的球半徑信息,您想到這些信息應該可以取得,並可以進一步取得球的體積,當您在Java中要定義這些信息時,您可以如下進行定義:
public class Ball { private double radius;//半徑 private String name;//名稱 //無參數構造函數 public Ball(){ this(0.0,"no name"); } //有參數構造函數 public Ball(double radius, String name) { this.radius = radius; this.name = name; } //獲取屬性 public double getRadius(){ return radius; } public String getName(){ return name; } //設置屬性 public void setRadius(double radius){ this.radius = radius; } public void setName(String name){ this.name = name; } }
一個定義良好的類別,即使在不看程序代碼實作的情況下,也可以從定義中所提供的公開(public)方法看出這個類別的大致功能。
在類別中的運算參與數據(Field)及互動方法(Method),我們統稱其為 類別成員(Class member)。
上例中的radius、name成員是field成員,getRadius()與getName()是method成員。注意到"public"這個關鍵 字,它表示所定義的成員可以使用宣告的對象名稱加上 '.' 運算子直接呼叫,也稱之為「公用成員」或「公開成員」。而private這個關鍵詞用來定義一個「私用成員」,它不可以透過參考名稱直接呼叫,又稱之為 「私有成員」。
在定義類別時,有一個基本原則是:信息的最小化公開。也就是說盡量透過方法來操作對象,而不是直接存取其內部運算參與數據(也就是field成員)。
信息的最小化公開原則是基於安全性的考慮,避免程序設計人員隨意操作field成員而造成程序的錯誤,您可以在日后的程序設計中慢慢來體會;在稍后的實作中,您將可以看到,我們將不會radius與name兩個私用成員直接進行存取,而是透過公開的方法來進行設定。
一個類別中的field成員,若宣告為"private",則其可視范圍(Scope)為整個類別,由於外界無法直接存取私用成員,所以您使用兩個公開方法 getRadius()與getName()分別傳回其這兩個成員的值。
與類別名稱同名的方法稱之為 建構方法 Cconstructor),也有人稱之為「建構子」,它沒有傳回值。顧名思義,建構方法的作用是讓您建構對象可以設定一些必要的建構信息,它可 以被重載(Overload),以滿足對象生成時不同的設定條件。
您在實作中重載了建構方法,在不指定參數的情況下,會將radius設定為0.0,而name設定為 "no name",另一個建構方法則可以指定參數,this()方法用於對象內部,表示呼叫對象的建構方法,另一個就是this,它表示對象本身,您可以在 關於 this 進一步了解其作用。
定義好類別之后,您就可根據這個類別(規格)來建構對象,建構對象時使用new關鍵詞,顧名思義,就是根據所指定的類別(規格書)「新建」一個對象:
Ball ball1 = new Ball(); Ball ball2 = new Ball(3.5, "red ball");
在上例中配置了ball1與ball2兩個對象,ball1對象在建立時並不指定任何參數,所以根據之前對Ball類別的定義,ball1的radius 將設定為0.0,name設定為"no name";ball2則給定兩個參數,所以ball2的radius設定為3.5,而ball2的name設定為"red ball"。
您可以透過公開成員來操作對象或取得對象信息,方法是使用對象名稱加上「.」運算子,例如:
ball1.getRadius(0.1);
ball1.setName("GBall");
以下先看個簡單的程序:
public class SimpleClass { public static void main(String[] args) { Ball b1 = new Ball(18.4, "red ball"); System.out.println("名稱: " + b1.getName()); System.out.println("半徑: " + b1.getRadius()); } }
類別與對象這兩個名詞會經常混於書籍與文件之中,例如「您可以使用Scanner類別」、「您可以使用Scanner對象」,這兩句在某些場合其語義是相 同的,不過要細究的話,兩句的意思通常都是「您可以使用根據Scanner類別所建構出來的對象」,不過寫這么長很煩,難免就省略了一些字眼。
Java會將參與內部運算的數據命名為field,其實是蠻有道理的,field在英文中有事件的參與者的意義,有限定范圍的意思。基本上,在定義對象 時,field成員其作用范圍要限定於對象之中,對對象內部數據的變更,都要透過公開方法來進行,避免field成員的作用范圍離開了對象之外。
32、類成員
在Java中,類別的存取權限修飾詞有"public"、"protected"、"private"三個,如果在宣告成員時不使用存取修飾詞,則預設以套件 (package)為存取范圍,也就是說在package外就無法存取,這些存取修飾,之后在 套件(package) 還會見到說明。
方法的參數列用來告知方法成員執行時所需的數據,如果傳入的自變量是基本數據型態(Primitive data type),則會將值復制至參數列上的變量,如果傳入的自變量是一個對象,則會將參數列上的變量參考至指定的對象。
Math.PI是由Java所提供的功能變量,它定義了圓周率3.14159......,在Math類別中還包括有許多公用的數學功能函式,您可以自行查詢 java.lang.Math 在線說明文件以得知這些功能。
另外可以注意到,autoboxing、 unboxing 在方法的參數列中是可以作用的,也就是說如果您的方法中是這樣設計的:
public class SomeClass { .... public void someMethod(Integer integer) { ...... } .... }
您可以使用這樣的方式來設定自變量:
SomeClass someObj = new SomeClass(); someObj.someMethod(1);
autoboxing、unboxing會自動作用,但記得要小心使用這個功能。
一般在命名類別時,類別名稱首字會大寫,而方法名稱首字是小寫,名稱命名時以一目了解名稱的作用為原則,上面所采取的都是駱駝式的命名方式,也就是每個單字的首字予以適當的大寫,例如someMethodOfSomeClass這樣的方式,這是常見的一種命名慣例。
為field成員設定setXXX()或getXXX()這類的方法時,XXX名稱最好與field名稱相對應,例如name這個field 成員對應的方法,可以命名為setName()與getName(),而radius這個成員,則對應於setRadius()與getRadius() 這樣的名稱,如此閱讀程序時可以一目了解setter與getter方法的存取對象。
33、static成員