Java學習筆記(1)——基礎語法


吃水不忘挖井人,我是根據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成員




免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM