Skip to content

Commit f40fc47

Browse files
author
Justin Lin
committed
completed ch6
1 parent b586265 commit f40fc47

File tree

2 files changed

+310
-1
lines changed

2 files changed

+310
-1
lines changed

docs/CH06.md

Lines changed: 310 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -319,4 +319,313 @@ StringBuilder 是 J2SE 5.0 才新增的類別,在 J2SE 5.0 之前的版本若
319319
6.2 字串進階運用
320320
---------------
321321

322-
待續 ...
322+
在瞭解字串的基本特性之後,接下來看看如何操作字串,除了 String 類別上的幾個可用方法之外,您還可以使用一些輔助的類別,像是「正則表示式」(Regular expression)於字串比對上的應用。
323+
324+
6.2.1 命令列引數
325+
326+
在文字模式下執行程式時,通常您可以連帶指定一些引數給程式,例如 javac 本身就附帶了一堆引數可以使用,直接鍵入javac就可以顯示每個引數所代表的意義。
327+
328+
![javac 可附帶的引數說明](../images/img06-07.png)
329+
330+
圖 6.7 javac 可附帶的引數說明
331+
332+
在文字模式下啟動一個 Java 程式時,您也可以一併指定引數,以讓程式進行相對應的功能,也就是輸入命令列引數(Command line argument)給程式,在您撰寫主程式時,會在 main() 的參數列撰寫 String[] args,目的就是用來接受一個字串陣列,您只要取得 args 中的元素值,就可以取出 Java 程式運行時被指定的引數,範例 6.6 是個簡單的示範。
333+
334+
#### **範例 6.6 CommandLineArg.java**
335+
```java
336+
public class CommandLineArg {
337+
public static void main(String[] args) {
338+
System.out.print("讀入的引數: ");
339+
for(int i = 0; i < args.length; i++)
340+
System.out.print(args[i] + " ");
341+
System.out.println();
342+
}
343+
}
344+
```
345+
346+
args 索引 0 的值是從程式名稱後第一個引數開始,以空白為區隔,依序地儲存在 args 陣列中,執行的方式與結果如下頁:
347+
348+
java CommandLineArg -file student.dat
349+
350+
讀入的引數: -file student.dat
351+
352+
當然您也可以使用 J2SE 5.0 新增的 foreach 語法來改寫範例 6.6,以循序取出被輸入的引數,範例 6.7 的執行結果與範例 6.6 是相同的。
353+
354+
#### **範例 6.7 CommandLineArg2.java**
355+
```java
356+
public class CommandLineArg2 {
357+
public static void main(String[] args) {
358+
System.out.print("讀入的引數: ");
359+
for(String arg : args)
360+
System.out.print(arg + " ");
361+
System.out.println();
362+
}
363+
}
364+
```
365+
> **良葛格的話匣子** 在寫 main() 方法中的參數列時,也可以寫成 String args[],這是陣列宣告取自於 C/C++ 的寫法,不過一般建議寫成 String[] args,比較符合 Java 的陣列宣告語法。
366+
> 由於命令列引數會傳遞給 args 陣列,如果您要存取 args 陣列的話,記得要檢查陣列長度,否則使用者若沒提供引數的話,會發生 ArrayIndexOutOfBoundsException 例外,一個檢查使用者是否有輸入引數的方式是檢查 args 陣列的長度,例如:
367+
>
368+
if(args.length == 0) {
369+
// 使用者沒有指定引數,顯示引數功能畫面
370+
}
371+
else {
372+
// 執行所指定引數的對應功能
373+
}
374+
375+
> 在第10章學到例外處理之後,您還可以使用try...catch語法來取代if判斷式。
376+
377+
## 6.2.2 分離字串
378+
379+
將字串依所設定的條件予以分離是很常見的操作,例如指令的分離、文字檔案的資料讀出等,以後者而言,當您在文字檔案中儲存以下的資料時,在讀入檔案後,將可以使用 String 的 split() 來協助每一格的資料分離。
380+
381+
justin 64/5/26 0939002302 5433343
382+
momor 68/7/23 0939100391 5432343
383+
384+
範例 6.8是個簡單的示範,假設 fakeFileData 的資料就是檔案中讀入的文字資料。
385+
386+
## **範例 6.8 SplitStringDemo.java**
387+
```java
388+
public class SplitStringDemo {
389+
public static void main(String args[]) {
390+
String[] fakeFileData = {
391+
"justin\t64/5/26\t0939002302\t5433343",
392+
"momor\t68/7/23\t0939100391\t5432343" };
393+
for(String data : fakeFileData) {
394+
String[] tokens = data.split("\t");
395+
for(String token : tokens) {
396+
System.out.print(token + "\t| ");
397+
}
398+
System.out.println();
399+
}
400+
}
401+
}
402+
```
403+
404+
執行結果:
405+
406+
justin | 64/5/26 | 0939002302 | 5433343 |
407+
momor | 68/7/23 | 0939100391 | 5432343 |
408+
409+
split() 依您所設定的分隔設定,將字串分為數個子字串並以 String 陣列傳回,這邊簡單的介紹了一下 split() 方法的使用,有些用過 Java 的人可能會想到 java.util.StringTokenizer,基本上 API 文件中明確的表示 StringTokenizer 已經是「遺產類別」(Legacy class)了,存在的原因是為了與舊版 Java 程式的相容性,不建議在您撰寫新的 Java 程式時使用,使用 split() 來代替會是個好的方案,而且您還可以進一步搭配正則表示式來進行字串分離。
410+
411+
## 6.2.3 使用正則表示式(Regular expression)
412+
413+
如果您查詢 J2SE 1.4 之後的 String 線上 API 手冊說明,您會發現有 matches()、replaceAll() 等方法,您所傳入的引數是「正則表示式」(Regular expression)的字串,正則表示式最早是由數學家 Stephen Kleene 于 1956 年提出,主要使用在字元字串的格式比對,後來在資訊領域廣為應用,現在已經成為 ISO(國際標準組織)的標準之一。
414+
415+
Java 在 J2SE 1.4 之後開始支援正則表示式,您可以在 API 文件的 java.util.regex.Pattern 類別中找到支援的正則表示式相關資訊;您可以將正則表示式應用於字串的比對、取代、分離等動作上,以下將介紹幾個簡單的正則表示式。
416+
417+
對於一些簡單的字元比對,例如 1 到 9、A-Z 等,您可以使用預先定義的符號來表示,表 6.4 列出幾個常用的字元比對符號。
418+
419+
#### **表 6.4 字元比對符號**
420+
| 方法 | 說明
421+
|:-- |:-
422+
| . | 符合任一字元
423+
| \d | 符合 0 到 9 任一個數字字元
424+
| \D | 符合 0-9 以外的字元
425+
| \s | 符合 '\t'、'\n'、'\x0B'、'\f'、'\r' 等空白字元
426+
| \w | 符合 a 到 z、A 到 Z、0 到 9 等字元,也就是數字或是字母都符合
427+
| \W | 符合 a 到 z、A 到 Z、0 到 9 等之外的字元,也就是除數字與字母外都符合
428+
429+
舉例來說,如果有一字串 "abcdebcadxbc",使用 ".bc" 來作比對的話,符合的子字串有 "abc"、"ebc"、"xbc" 三個;如果使用 "..cd",則符合的子字串只有 "abcd",範例 6.9證實這個說明。
430+
431+
#### **範例6.9 RegularExpressionDemo.java**
432+
```java
433+
public class RegularExpressionDemo {
434+
public static void main(String[] args) {
435+
String text = "abcdebcadxbc";
436+
437+
String[] tokens = text.split(".bc");
438+
for(String token : tokens) {
439+
System.out.print(token + " ");
440+
}
441+
System.out.println();
442+
443+
tokens = text.split("..cd");
444+
for(String token : tokens) {
445+
System.out.print(token + " ");
446+
}
447+
System.out.println();
448+
449+
}
450+
}
451+
```
452+
453+
執行結果:
454+
455+
d ad
456+
ebcadxbc
457+
458+
使用 ".bc" 來作比對的話,由於符合的子字串有 "abc"、"ebc"、"xbc" 三個,所以 split() 方法會使用這三個字串為依據來作字串分離,傳回的自然就是不符合表示式 ".bc" 的 "d" 與 "ad",同理如果表示式為 "..cd",則使用 split() 傳回的就是不符合 "..cd" 的 "ebcadxbc"。
459+
460+
您也可以使用「字元類」(Character class)來比較一組字元範圍,表 6.5 示範了幾個字元類的設定方式。
461+
462+
#### **表 6.5 字元類範例**
463+
| 範例 | 作用
464+
|:- |:-
465+
| [abc] | 符合 a、b 或 c
466+
| [^abc] | 符合「a 或 b 或 c」之外的字元
467+
| [a-zA-Z] | 符合 a 到 z 或者是 A 到 Z 的字元
468+
| [a-d[m-p]] | a 到 d 或者是m 到 p,也可以寫成 [a-dm-p]
469+
| [a-z&&[def]] | a 到 z 並且是 d 或 e 或 f,結果就是 d 或 e 或 f 可以符合
470+
| [a-z&&[^bc]] | a 到 z 並且不是 b 或 c
471+
| [a-z&&[^m-p]] | a 到 z 並且不是 m 到 p
472+
473+
指定一個字元之外,您也可以加上「貪婪量詞」(Greedy quantifiers)來指定字元可能出現的次數,表 6.6 示範了幾個例子。
474+
475+
#### **表 6.6 貪婪量詞範例**
476+
| 範例 | 作用
477+
|:- |:-
478+
| X? | X 可出現一次或完全沒有
479+
| X* | X 可出現零次或多次
480+
| X+ | X 可出現一次或多次
481+
| X{n} | X 可出現 n 次
482+
| X{n,} | X 可出現至少n次
483+
| X{n, m} | X 可出現至少 n 次,但不超過 m 次
484+
| X? | X 可出現一次或完全沒有
485+
486+
另外還有 Reluctant quantifiers、Possessive quantifiers 等的指定,您可以自行參考 java.util.regex.Pattern 類別 API 文件中的說明。
487+
488+
在 String 類別中,matches() 方法可以讓您驗證字串是否符合指定的正則表示式,這通常用於驗證使用者輸入的字串資料是否正確,例如電話號碼格式;replaceAll() 方法可以將符合正則表示式的子字串置換為指定的字串;split() 方法可以讓您依指定的正則表示式,將符合的子字串排除,剩下的子字串分離出來並以字串陣列傳回,範例 6.9 已經示範了 split() 方法的使用,接下來在範例 6.10 中示範了 replaceAll() 與 matches() 方法的運用。
489+
490+
#### **範例 6.10 UseRegularExpression.java**
491+
```java
492+
import java.io.*;
493+
494+
public class UseRegularExpression {
495+
public static void main(String args[])
496+
throws IOException {
497+
BufferedReader reader =
498+
new BufferedReader(
499+
new InputStreamReader(System.in));
500+
501+
System.out.println("abcdefgabcabc".replaceAll(".bc", "###"));
502+
503+
String phoneEL = "[0-9]{4}-[0-9]{6}";
504+
String urlEL = "<a.+href*=*['\"]?.*?['\"]?.*?>";
505+
String emailEL = "^[_a-z0-9-]+(.[_a-z0-9-]+)*" +
506+
"@[a-z0-9-]+([.][a-z0-9-]+)*$";
507+
508+
System.out.print("輸入手機號碼: ");
509+
String input = reader.readLine();
510+
511+
if(input.matches(phoneEL))
512+
System.out.println("格式正確");
513+
else
514+
System.out.println("格式錯誤");
515+
516+
System.out.print("輸入href標籤: ");
517+
input = reader.readLine();
518+
519+
// 驗證href標籤
520+
if(input.matches(urlEL))
521+
System.out.println("格式正確");
522+
else
523+
System.out.println("格式錯誤");
524+
525+
System.out.print("輸入電子郵件: ");
526+
input = reader.readLine();
527+
528+
// 驗證電子郵件格式
529+
if(input.matches(emailEL))
530+
System.out.println("格式正確");
531+
else
532+
System.out.println("格式錯誤");
533+
}
534+
}
535+
```
536+
537+
執行結果:
538+
539+
###defg######
540+
輸入手機號碼: 0939-100391
541+
格式正確
542+
輸入href標籤: <a href="http://caterpillar.onlyfun.net">
543+
格式正確
544+
輸入電子郵件: caterpillar.onlyfun@gmail.com
545+
格式正確
546+
547+
> **良葛格的話匣子** 正則表示式的設計是門學問,也有專書就是在介紹正則表示式的設計,沒有經常使用的話,很難設計出實用性高的正則表示式,這邊只能說大致介紹而已,如果真有需要某種正則表示式,建議可以使用搜尋引擎找看看有無現成或類似的表示式可以使用,要不然的話,就只有找專書研究研究如何設計了。
548+
549+
## 6.2.4 Pattern、Matcher
550+
551+
String 上可使用正則表示式的操作,實際上是利用了 java.util.regex.Pattern 與 java.util.regex.Matcher 的功能,當您呼叫 String 的 matches() 方法時,實際上是呼叫 Pattern 的靜態方法 matches(),這個方法會傳回 boolean 值,表示字串是否符合正則表示式。
552+
553+
如果您想要將正則表示式視為一個物件來重複使用,則您可以使用 Pattern 的靜態方法 compile() 進行編譯,compile() 方法會傳回一個 Pattern 的實例,這個實例代表您的正則表示式,之後您就可以重複使用 Pattern 實例的 matcher() 方法來傳回一個 Matcher 的實例,代表符合正則式的的實例,這個實例上有一些尋找符合正則式條件的方法可供操作,範例 6.11 直接作了示範。
554+
555+
#### **範例 6.11 UsePatternMatcher.java**
556+
```java
557+
import java.util.regex.*;
558+
559+
public class UsePatternMatcher {
560+
public static void main(String[] args) {
561+
String phones1 =
562+
"Justin 的手機號碼:0939-100391\n" +
563+
"momor 的手機號碼:0939-666888\n";
564+
565+
Pattern pattern = Pattern.compile(".*0939-\\d{6}");
566+
Matcher matcher = pattern.matcher(phones1);
567+
568+
while(matcher.find()) {
569+
System.out.println(matcher.group());
570+
}
571+
572+
String phones2 =
573+
"caterpillar 的手機號碼:0952-600391\n" +
574+
"bush 的手機號碼:0939-550391";
575+
576+
matcher = pattern.matcher(phones2);
577+
578+
while(matcher.find()) {
579+
System.out.println(matcher.group());
580+
}
581+
}
582+
}
583+
```
584+
585+
範例 6.11 會尋找手機號碼為 0939 開頭的號碼,假設您的號碼來源不只一個(如 phones1、phones2),則您可以編譯好正則表示式並傳回一個 Pattern 物件,之後就可以重複使用這個 Pattern 物件,在比對時您使用 matcher() 傳回符合條件的 Matcher 實例,find() 方法表示是否有符合的字串,group() 方法則可以將符合的字串傳回,程式的執行結果如下:
586+
587+
Justin 的手機號碼:0939-100391
588+
momor 的手機號碼:0939-666888
589+
bush 的手機號碼:0939-550391
590+
591+
來使用 Pattern 與 Matcher 改寫一下範例 6.9,讓程式可以傳回符合正則式的字串,而不是傳回不符合的字串。
592+
593+
#### **範例 6.12 RegularExpressionDemo2.java**
594+
```java
595+
import java.util.regex.*;
596+
597+
public class RegularExpressionDemo2 {
598+
public static void main(String[] args) {
599+
String text = "abcdebcadxbc";
600+
601+
Pattern pattern = Pattern.compile(".bc");
602+
Matcher matcher = pattern.matcher(text);
603+
604+
while(matcher.find()) {
605+
System.out.println(matcher.group());
606+
}
607+
System.out.println();
608+
}
609+
}
610+
```
611+
612+
執行結果:
613+
614+
abc
615+
ebc
616+
xbc
617+
618+
> **良葛格的話匣子** 在這邊想再嘮叨一下,在書中我有介紹的類別上之方法操作,都只是 API 中一小部份而已,目的只是讓您瞭解有這個功能的存在,並以實際的範例體會其功能,真正要瞭解每個方法的操作,「請多參考線上 API 文件」,我不想列出一長串的表格來說明每個方法,這只會讓篇幅增加,也只會模糊了您學習的焦點,而且請記得,學會查詢 API 文件,絕對是學好 Java 的必備功夫。
619+
620+
6.3 接下來的主題
621+
---------------
622+
623+
每一個章節的內容由淺至深,初學者該掌握的深度要到哪呢?在這個章節中,對於初學者我建議至少掌握以下幾點內容:
624+
625+
- 會使用 String 類別上的常用方法
626+
- 瞭解字串的不可變數變動(immutable)特性
627+
- 知道有字串池的存在
628+
- 瞭解使用 '==' 與 equals() 方法比較兩個字串時的不同點
629+
- 瞭解 '+' 使用於字串串接上的負擔
630+
631+
到目前為止,您僅止於使用物件的階段,接下來就要進入重頭戲了,從下一個章節開始,您將會逐步接觸 Java 中物件導向程式設計的領域,瞭解 Java 中的何種特性可以支援物件導向設計。

images/img06-07.png

142 KB
Loading

0 commit comments

Comments
 (0)