Java SE 6 技術手冊

6.2 字串進階運用

在瞭解字串的基本特性之後,接下來看看如何操作字串,除了 String 類別上的幾個可用方法之外,您還可以使用一些輔助的類別,像是「正則表示式」(Regular expression)於字串比對上的應用。

6.2.1 命令列引數

在文字模式下執行程式時,通常您可以連帶指定一些引數給程式,例如 javac 本身就附帶了一堆引數可以使用,直接鍵入javac就可以顯示每個引數所代表的意義。

javac 可附帶的引數說明

圖 6.7 javac 可附帶的引數說明

在文字模式下啟動一個 Java 程式時,您也可以一併指定引數,以讓程式進行相對應的功能,也就是輸入命令列引數(Command line argument)給程式,在您撰寫主程式時,會在 main() 的參數列撰寫 String[] args,目的就是用來接受一個字串陣列,您只要取得 args 中的元素值,就可以取出 Java 程式運行時被指定的引數,範例 6.6 是個簡單的示範。

範例 6.6 CommandLineArg.java

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(); 
    } 
}

args 索引 0 的值是從程式名稱後第一個引數開始,以空白為區隔,依序地儲存在 args 陣列中,執行的方式與結果如下頁:

java CommandLineArg -file student.dat

讀入的引數: -file student.dat

當然您也可以使用 J2SE 5.0 新增的 foreach 語法來改寫範例 6.6,以循序取出被輸入的引數,範例 6.7 的執行結果與範例 6.6 是相同的。

範例 6.7 CommandLineArg2.java

public class CommandLineArg2 { 
    public static void main(String[] args) { 
        System.out.print("讀入的引數: "); 
        for(String arg : args)
            System.out.print(arg + " "); 
        System.out.println(); 
    } 
}

良葛格的話匣子 在寫 main() 方法中的參數列時,也可以寫成 String args[],這是陣列宣告取自於 C/C++ 的寫法,不過一般建議寫成 String[] args,比較符合 Java 的陣列宣告語法。 由於命令列引數會傳遞給 args 陣列,如果您要存取 args 陣列的話,記得要檢查陣列長度,否則使用者若沒提供引數的話,會發生 ArrayIndexOutOfBoundsException 例外,一個檢查使用者是否有輸入引數的方式是檢查 args 陣列的長度,例如:

if(args.length == 0) {
    // 使用者沒有指定引數,顯示引數功能畫面
}
else {
    // 執行所指定引數的對應功能
}

在第10章學到例外處理之後,您還可以使用try...catch語法來取代if判斷式。

6.2.2 分離字串

將字串依所設定的條件予以分離是很常見的操作,例如指令的分離、文字檔案的資料讀出等,以後者而言,當您在文字檔案中儲存以下的資料時,在讀入檔案後,將可以使用 String 的 split() 來協助每一格的資料分離。

justin    64/5/26    0939002302    5433343
momor    68/7/23    0939100391    5432343

範例 6.8是個簡單的示範,假設 fakeFileData 的資料就是檔案中讀入的文字資料。

範例 6.8 SplitStringDemo.java

public class SplitStringDemo { 
    public static void main(String args[]) { 
        String[] fakeFileData = {
          "justin\t64/5/26\t0939002302\t5433343",
          "momor\t68/7/23\t0939100391\t5432343" }; 
        for(String data : fakeFileData) {
            String[] tokens = data.split("\t");
             for(String token : tokens) {
                System.out.print(token + "\t| ");
            }
            System.out.println(); 
        }
    } 
}

執行結果:

justin  | 64/5/26       | 0939002302    | 5433343       |
momor     | 68/7/23       | 0939100391    | 5432343       |

split() 依您所設定的分隔設定,將字串分為數個子字串並以 String 陣列傳回,這邊簡單的介紹了一下 split() 方法的使用,有些用過 Java 的人可能會想到 java.util.StringTokenizer,基本上 API 文件中明確的表示 StringTokenizer 已經是「遺產類別」(Legacy class)了,存在的原因是為了與舊版 Java 程式的相容性,不建議在您撰寫新的 Java 程式時使用,使用 split() 來代替會是個好的方案,而且您還可以進一步搭配正則表示式來進行字串分離。

6.2.3 使用正則表示式(Regular expression)

如果您查詢 J2SE 1.4 之後的 String 線上 API 手冊說明,您會發現有 matches()、replaceAll() 等方法,您所傳入的引數是「正則表示式」(Regular expression)的字串,正則表示式最早是由數學家 Stephen Kleene 于 1956 年提出,主要使用在字元字串的格式比對,後來在資訊領域廣為應用,現在已經成為 ISO(國際標準組織)的標準之一。

Java 在 J2SE 1.4 之後開始支援正則表示式,您可以在 API 文件的 java.util.regex.Pattern 類別中找到支援的正則表示式相關資訊;您可以將正則表示式應用於字串的比對、取代、分離等動作上,以下將介紹幾個簡單的正則表示式。

對於一些簡單的字元比對,例如 1 到 9、A-Z 等,您可以使用預先定義的符號來表示,表 6.4 列出幾個常用的字元比對符號。

表 6.4 字元比對符號

方法 說明
. 符合任一字元
\d 符合 0 到 9 任一個數字字元
\D 符合 0-9 以外的字元
\s 符合 '\t'、'\n'、'\x0B'、'\f'、'\r' 等空白字元
\w 符合 a 到 z、A 到 Z、0 到 9 等字元,也就是數字或是字母都符合
\W 符合 a 到 z、A 到 Z、0 到 9 等之外的字元,也就是除數字與字母外都符合

舉例來說,如果有一字串 "abcdebcadxbc",使用 ".bc" 來作比對的話,符合的子字串有 "abc"、"ebc"、"xbc" 三個;如果使用 "..cd",則符合的子字串只有 "abcd",範例 6.9證實這個說明。

範例6.9 RegularExpressionDemo.java

public class RegularExpressionDemo {
    public static void main(String[] args) {
        String text = "abcdebcadxbc";

        String[] tokens = text.split(".bc");
        for(String token : tokens) {
            System.out.print(token + " ");
        }
        System.out.println();

        tokens = text.split("..cd");
        for(String token : tokens) {
            System.out.print(token + " ");
        }
        System.out.println();

    }
}

執行結果:

d ad
ebcadxbc

使用 ".bc" 來作比對的話,由於符合的子字串有 "abc"、"ebc"、"xbc" 三個,所以 split() 方法會使用這三個字串為依據來作字串分離,傳回的自然就是不符合表示式 ".bc" 的 "d" 與 "ad",同理如果表示式為 "..cd",則使用 split() 傳回的就是不符合 "..cd" 的 "ebcadxbc"。

您也可以使用「字元類」(Character class)來比較一組字元範圍,表 6.5 示範了幾個字元類的設定方式。

表 6.5 字元類範例

範例 作用
[abc] 符合 a、b 或 c
abc 符合「a 或 b 或 c」之外的字元
[a-zA-Z] 符合 a 到 z 或者是 A 到 Z 的字元
[a-d[m-p]] a 到 d 或者是m 到 p,也可以寫成 [a-dm-p]
[a-z&&[def]] a 到 z 並且是 d 或 e 或 f,結果就是 d 或 e 或 f 可以符合
[a-z&&bc] a 到 z 並且不是 b 或 c
[a-z&&m-p] a 到 z 並且不是 m 到 p

指定一個字元之外,您也可以加上「貪婪量詞」(Greedy quantifiers)來指定字元可能出現的次數,表 6.6 示範了幾個例子。

表 6.6 貪婪量詞範例

範例 作用
X? X 可出現一次或完全沒有
X* X 可出現零次或多次
X+ X 可出現一次或多次
X{n} X 可出現 n 次
X{n,} X 可出現至少n次
X{n, m} X 可出現至少 n 次,但不超過 m 次
X? X 可出現一次或完全沒有

另外還有 Reluctant quantifiers、Possessive quantifiers 等的指定,您可以自行參考 java.util.regex.Pattern 類別 API 文件中的說明。

在 String 類別中,matches() 方法可以讓您驗證字串是否符合指定的正則表示式,這通常用於驗證使用者輸入的字串資料是否正確,例如電話號碼格式;replaceAll() 方法可以將符合正則表示式的子字串置換為指定的字串;split() 方法可以讓您依指定的正則表示式,將符合的子字串排除,剩下的子字串分離出來並以字串陣列傳回,範例 6.9 已經示範了 split() 方法的使用,接下來在範例 6.10 中示範了 replaceAll() 與 matches() 方法的運用。

範例 6.10 UseRegularExpression.java

import java.io.*;

public class UseRegularExpression { 
    public static void main(String args[]) 
                              throws IOException { 
        BufferedReader reader = 
            new BufferedReader(
                new InputStreamReader(System.in));

        System.out.println("abcdefgabcabc".replaceAll(".bc", "###")); 

        String phoneEL = "[0-9]{4}-[0-9]{6}";
        String urlEL = "<a.+href*=*['\"]?.*?['\"]?.*?>";
        String emailEL = "^[_a-z0-9-]+(.[_a-z0-9-]+)*" + 
            "@[a-z0-9-]+([.][a-z0-9-]+)*$";

        System.out.print("輸入手機號碼: "); 
        String input = reader.readLine();

        if(input.matches(phoneEL)) 
            System.out.println("格式正確"); 
        else 
            System.out.println("格式錯誤");

        System.out.print("輸入href標籤: "); 
        input = reader.readLine();

        // 驗證href標籤 
        if(input.matches(urlEL))
            System.out.println("格式正確"); 
        else
            System.out.println("格式錯誤");

        System.out.print("輸入電子郵件: "); 
        input = reader.readLine();

        // 驗證電子郵件格式 
        if(input.matches(emailEL))
            System.out.println("格式正確"); 
        else
            System.out.println("格式錯誤"); 
    } 
}

執行結果:

###defg######
輸入手機號碼: 0939-100391
格式正確
輸入href標籤: <a href="http://caterpillar.onlyfun.net">
格式正確
輸入電子郵件: [email protected]
格式正確

良葛格的話匣子 正則表示式的設計是門學問,也有專書就是在介紹正則表示式的設計,沒有經常使用的話,很難設計出實用性高的正則表示式,這邊只能說大致介紹而已,如果真有需要某種正則表示式,建議可以使用搜尋引擎找看看有無現成或類似的表示式可以使用,要不然的話,就只有找專書研究研究如何設計了。

6.2.4 Pattern、Matcher

String 上可使用正則表示式的操作,實際上是利用了 java.util.regex.Pattern 與 java.util.regex.Matcher 的功能,當您呼叫 String 的 matches() 方法時,實際上是呼叫 Pattern 的靜態方法 matches(),這個方法會傳回 boolean 值,表示字串是否符合正則表示式。

如果您想要將正則表示式視為一個物件來重複使用,則您可以使用 Pattern 的靜態方法 compile() 進行編譯,compile() 方法會傳回一個 Pattern 的實例,這個實例代表您的正則表示式,之後您就可以重複使用 Pattern 實例的 matcher() 方法來傳回一個 Matcher 的實例,代表符合正則式的的實例,這個實例上有一些尋找符合正則式條件的方法可供操作,範例 6.11 直接作了示範。

範例 6.11 UsePatternMatcher.java

import java.util.regex.*;

public class UsePatternMatcher {
    public static void main(String[] args) {
        String phones1 = 
              "Justin 的手機號碼:0939-100391\n" +
              "momor 的手機號碼: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 的手機號碼:0952-600391\n" +
             "bush 的手機號碼:0939-550391";

        matcher = pattern.matcher(phones2);

        while(matcher.find()) {
            System.out.println(matcher.group());
        }
    }
}

範例 6.11 會尋找手機號碼為 0939 開頭的號碼,假設您的號碼來源不只一個(如 phones1、phones2),則您可以編譯好正則表示式並傳回一個 Pattern 物件,之後就可以重複使用這個 Pattern 物件,在比對時您使用 matcher() 傳回符合條件的 Matcher 實例,find() 方法表示是否有符合的字串,group() 方法則可以將符合的字串傳回,程式的執行結果如下:

Justin 的手機號碼:0939-100391
momor 的手機號碼:0939-666888
bush 的手機號碼:0939-550391

來使用 Pattern 與 Matcher 改寫一下範例 6.9,讓程式可以傳回符合正則式的字串,而不是傳回不符合的字串。

範例 6.12 RegularExpressionDemo2.java

import java.util.regex.*;

public class RegularExpressionDemo2 {
    public static void main(String[] args) {
        String text = "abcdebcadxbc";

        Pattern pattern = Pattern.compile(".bc");
        Matcher matcher = pattern.matcher(text);

        while(matcher.find()) {
            System.out.println(matcher.group());
        }
        System.out.println();
    }
}

執行結果:

abc
ebc
xbc

良葛格的話匣子 在這邊想再嘮叨一下,在書中我有介紹的類別上之方法操作,都只是 API 中一小部份而已,目的只是讓您瞭解有這個功能的存在,並以實際的範例體會其功能,真正要瞭解每個方法的操作,「請多參考線上 API 文件」,我不想列出一長串的表格來說明每個方法,這只會讓篇幅增加,也只會模糊了您學習的焦點,而且請記得,學會查詢 API 文件,絕對是學好 Java 的必備功夫。