@@ -319,4 +319,313 @@ StringBuilder 是 J2SE 5.0 才新增的類別,在 J2SE 5.0 之前的版本若
319
319
6.2 字串進階運用
320
320
---------------
321
321
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\t 64/5/26\t 0939002302\t 5433343" ,
392
+ " momor\t 68/7/23\t 0939100391\t 5432343" };
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 中的何種特性可以支援物件導向設計。
0 commit comments