Upload
r82093403
View
173
Download
7
Embed Size (px)
DESCRIPTION
Citation preview
本投影片(下稱教用資源)僅授權給採用教用資源相關之旗標書籍為教科書之授課老師(下稱老師)專用,老師為教學使用之目的,得摘錄、編輯、重製教用資源(但使用量不得超過各該教用資源內容之 80% )以製作為輔助教學之教學投影片,並於授課時搭配旗標書籍公開播放,但不得為網際網路公開傳輸之遠距教學、網路教學等之使用;除此之外,老師不得再授權予任何第三人使用,並不得將依此授權所製作之教學投影片之相關著作物移作他用。
著作權所有 © 旗標出版股份有限公司
字串 (String)
第 10 章
2
學習目標
瞭解 String 類別 熟練 String 類別所提供的方法 認識 StringBuffer 與 StringBuilder 類別 使用規則表示法 (Regular Expression)
3
前言
在第 3 章中 , 曾經簡短地介紹過字串這種資料型別 , 而且在前幾章的範例中幾乎也都使用到字串 , 大家應該對於字串都不陌生。
在這一章中 , 我們要針對字串其實是 String 物件這一件事加以說明 , 並且會介紹 String 類別所提供用來處理字串內容的許多方法。
最後 , 還會介紹專責比對字串的規則表示法 (Regular E
xpression), 讓大家可以善用字串。 學會本章介紹的各種 String 類別應用後 , 讀者會發現 ,
不需瞭解 String 類別是如何設計 / 運作 , 我們就能善加利用它 , 相信讀者也更能體會資訊隱藏的妙用。
4
10-1 字串的產生
字串其實就是 String 物件 , 所以宣告一個字串變數 ,
就等於是宣告一個指到 String 物件的參照 , 然後再產生 String 物件。
為了要能正確的產生物件 , 首先來看看 String 類別所定義常用的建構方法。
8
字串的產生
1. 第 6 行使用不需參數的建構方法建構出來的就是空的字串 , 也就是一個內容為 0 個字元的字串。
2. 第 7 行由 test 所指字元陣列建構字串 , 因此建構出的字串內容為 " 這是個測試字串 " 。
3. 第 8 行由 test 所指字元陣列中索引碼為 3 的元素開始 , 取出 4 個元素建構字串。由於陣列元素索引碼是從 0 起算 , 所以建構出來的字串為 " 測試字串 " 。
9
字串的產生
4. 第 9 行由剛剛建立的字串 b 產生副本 , 因此內容一樣。
5. 第 17 行是特別展示 , 讓大家瞭解雖然字串 d 和字串 b 的內容一樣 , 但卻是不同的物件個體 , 所以 == 運算比較參照值的結果並不相等。如果要進行字串內容的比對 , 必須使用稍後會介紹的 equal
s() 方法。
10
10-1-1 Java 對於 String 類別的特別支援
從剛剛的描述可以想見 , 對於像是字串這樣常用的資料型別 , 如果要一一使用建構方法來建立物件其實並不方便 , 因此 , Java 語言對於 String 類別提供了幾個特別的輔助。
12
使用字面常數建立 String 物件
其中第 4 行就是直接使用字面常數建立物件。當程式中有字面常數時 , Java 編譯器其實會產生一個 St
ring 物件來代表所有相同內容的字面常數字串。 也就是說 , 第 5 行設定給 b 的參照值其實和給 a
的是一樣的 , 都指向同一個 String 物件;而第 6 行傳給 String 類別建構方法的也是同一個物件。
13
使用字面常數建立 String 物件 您可以把這 3 行看成是這樣:
因此 , 第 8 行的 a == b 就會是 true, 因為 a 和 b 指向同一個物件 , 參照值相等。
但是 c 則是建立副本 , 指向另一個新物件 , 所以不論是 a == c 或是 b == c 都不會相同。
如果要比對字串的內容 , 就必須使用 String 類別的equals() 方法。
16
使用字面常數建立 String 物件
對於英文字串 , 則有另一個 equalsIgnoreCase() 方法 , 可在不分大小寫的情況下 , 進行字串比對。
亦即用 equals() 方法比對時 , "ABC" 和 "abc" 會被視為不同;但用 equalsIgnoreCase() 方法 , 則會將 "ABC" 和 "abc" 視為相同 , 例如執行『 "ABC".
equalsIgnoreCase("abc")』會傳回 True 。
18
自動轉型 (Implicit Conversion)
搭配連接運算使用時 , 如果連接的運算元中有非 Str
ing 物件 , Java 會嘗試將該運算元轉換為 String 物件 , 轉換的方式就是呼叫該運算元的 toString() 方法 , 例如。
20
自動轉型 (Implicit Conversion)
要注意的是 , toString() 方法必須傳回 String 物件 ,
而且必須加上 public 存取控制。▪ 若是字串與基本資料型別的變數做連接運算 , 則該變數會被包裝成對應類別的物件 , 再呼叫該類別的 toSt
ring() 方法 , 詳見第 11-4-2 節。
21
String 物件的內容無法更改
String 物件一旦產生之後 , 其內容就無法更改 , 即便是連接運算 , 都是以運算元連接之後的字串產生新的 String 物件作為運算結果。
除此之外 , String 類別所提供的各個方法也都是傳回一個新的字串 , 而不是直接更改字串的內容。
如果需要能夠更改字串內容的物件 , 必須使用 10-3
節會介紹的 StringBuffer 或是 StringBuilder 類別。
22
10-2 String 類別的方法
String 類別提供許多處理字串的方法 , 可以幫助您有效的使用字串 , 我們將在這一節為您介紹一些重要的方法 , 相關資訊可以在 JDK 的說明文件中找到。
要特別再提醒讀者 , 以下傳回值型別為 String 的方法都是『傳回副本』 , 而不會修改原本的字串內容。
25
int compareTo(String anotherString)
以逐字元方式 (Lexically) 與 anotherString 所指字串的內容比較 , 如果 anotherString 比較大 , 就傳回一個負數值。
如果字串內容完全相同 , 就傳回 0 ;如果 another
String 比較小 , 就傳回一個正數值。
26
int compareTo(String anotherString)
至於兩個字串 a 與 b 之間的大小 , 是依照以下的規則來決定:1. 由索引 0 開始 , 針對 a 與 b 相同索引碼的字元逐一比較其標準萬國碼 (Unicode), 一旦遇到相同位置但字元不同時 , 就以此位置的字元相比較決定 a 與 b 的順序。例如 , a 為 "abcd" 、 b 為 "abed", 索引碼 0 、 1 這兩個位置的字元皆相同 , 但索引碼 2 的地方 a 為 'c' 、 b 為 'e', 所以 b 比 a 大。
2. 如果 a 與 b 的長度相同 , 且逐一字元比較後 , 同位置的字元皆相同 , 就傳回 0 。此時 , a.equals(b) 或是 b.equals(a) 皆為 true 。
27
int compareTo(String anotherString)
3. 如果 a 與 b 長度不同 , 且逐一字元比較後 , 較短的一方完全和較長的一方前面部分相同 , 就比較 a
與 b 的長度決定大小。例如 , 如果 a 為 "abc" 、b 為 "abcd", 那麼 a 就小於 b 。
4. 在標準萬國碼中 , 英文字母的順序就是字碼的順序 ,
另外 , 大寫字母是排在小寫字母前面 , 所以相同字母時 , 小寫大於大寫。
29
int compareTo(String anotherString)
與 equals() 方法類似 , Compareto() 方法也有一個雙胞胎 compareToIgnoreCase(), 在比較時會將同一字母大小寫視為相同。
31
甚麼是 CharSequence 類別 ( 上面方法的參數型別 )
CharSequence 其實並不是類別 , 而是一個介面 (In
terface) 。 我們會在第 12 章介紹介面 , 這裡您只要知道所有出現 CharSequence 型別參數的地方 , 都表示該參數可以是 String 或是 StringBuilder 、 StringBuffer
類別的物件即可。
33
void getChars(int srcBegin, int srcEnd, char[ ] dst, int dstBegin)
將索引碼 srcBegin 到 srcEnd - 1 的字元 , 複製到 dst 所指字元陣列、由索引碼 dstBegin 開始的元素中。
37
int indexOf(int ch, int fromIndex)
indexOf() 方法的多重定義版本 , 可以透過 fromInd
ex 指定開始尋找的位置。 只要結合這 2 種 indexOf() 方法 , 就可以逐一找出字串中所有出現指定字元的位置了。
這個方法也有個雙胞胎的版本 , 叫做 lastIndexOf(),
可以從字串尾端往前尋找。
40
int indexOf(String str, int fromIndex)
indexOf() 方法的多重定義版本 , 可以透過 fromInd
ex 指定開始尋找的位置。 只要結合這 2 種 indexOf() 方法 , 就可以逐一找出所有出現指定字串的位置了。
當然也有個對應的 lastIndexOf() 方法 , 可以從字串尾端往前尋找。
42
String replace(char oldChar, char newChar)
將字串中所有出現 oldChar 所指定的字元取代為由 newChar 所指定的字元。
要提醒您的是 , 這並不會更改原始字串的內容 , 而是將取代的結果以新的字串傳回。
43
String replace(CharSequence target, CharSequence replacement)
和上一個方法功能類似 , 但是將字串中所有出現 tar
get 所指字串內容的地方都取代為 replacement 所指字串的內容。
44
boolean startsWith(String prefix)boolean startsWith(String prefix, int offset)
startsWith() 的用法和前面看過的 endsWith() 類似 ,
但功能相反 , startsWith() 是用來檢查目前字串是否是以參數字串 prefix 開頭。
較特別的是 startsWith() 有兩個參數的版本 , 可指定從索引位置 offset 開始 , 檢查是否以參數字串 p
refix 為開頭。
47
String substring(int beginIndex, int endIndex)
傳回由 beginIndex 所指定的索引碼開始到 endIndex - 1 所指定的索引碼為止的部分字串。
52
10-3 StringBuffer 與 StringBuilder 類別
前 2 節我們一直強調 , String 物件無法更改其字串內容 , 這主要是因為如此一來 , String 物件就不需要因為字串內容變長或是變短時 , 必須進行重新配置儲存空間的動作。
但如果您必須使用可以隨時更改內容的字串 , 那麼就必須改用 StringBuffer 或是 StringBuilder 類別。
53
10-3-1 StringBuffer 類別
基本上 , 我們可以把 StringBuffer 類別看成是『可改變內容的 String 類別』。
因此 StringBuffer 類別提供了各種可改變字串內容的方法 , 像是可新增內容到字串中的 append() 及 insert() 、可刪除字串內容的 delete() 。
以下先來看 StringBuffer 類別的建構方法:
55
StringBuffer 類別
還記得在 10-5 頁提過 , Java 會產生一個 String
物件來代替程式中的字面常數 , 所以第 4 、 5 行也可直接寫成:
以下就來介紹 StringBuffer 類別的修改字串方法 ,
這些方法不但會直接修改 StringBuffer 物件的內容 ,
也會將修改後的結果傳回。
56
append() 方法
StringBuffer 物件並不能使用 "+" 運算子來連接字串 , 而必須使用 append() 或是 insert() 方法。
append() 方法會在字串尾端添加資料 , 並且擁有多重定義的版本 , 可以傳入基本型別、 String 物件以及其他有定義 toString() 方法的物件。
它會將傳入的參數轉成字串 , 添加到目前字串的尾端 ,
然後傳回自己。
58
insert() 方法
insert() 方法和 append() 方法一樣有多種版本 , 但是它可以透過第 1 個參數 offset 將第 2 個參數插入到字串中的特定位置。
offset 代表的是索引碼 , insert() 方法會把資料插入到 offset 所指的位置之前。
62
StringBuffer replace(int start, int end, String str)
將 start 所指定索引碼開始到 end - 1 所指定索引碼之間的一段字元取代為 str 所指定的字串。
64
void setCharAt(int index, char ch)
將 index 所指定索引碼的字元取代成 ch 所指定的字元。
請特別注意 , 這是唯一一個更改了字串內容 , 但卻沒有傳回自己的方法 , 在使用時要特別小心。
66
其他方法
StringBuffer 也提供下列方法 , 其用法和 String 類別的同名方法相同 (請注意 , 這些方法都不會更改到物件本身的內容 , 也不會傳回 StringBuffer 物件 ) :▪ char charAt (int index)
▪ void getChars (int srcBegin, int srcEnd, char[ ] dst, int
dstBegin)
▪ int indexOf (String str)
▪ int indexOf (String str, int fromIndex)
67
其他方法
▪ int lastIndexOf (String str)
▪ int lastIndexOf (String str, int fromIndex)
▪ int length ()
▪ String substring (int start)
▪ String substring (int start, int end)
68
10-3-2 StringBuilder 類別
這個類別和 StringBuffer 的用途相同 , 且提供的方法一模一樣 , 唯一的差別就是此類別並不保證在多執行緒的環境下可以正常運作 , 有關多執行緒 , 請參考第 15 章。
如果您使用字串的場合不會有多個執行緒共同存取同一字串的話 , 建議可以改用 StringBuilder 類別 , 以得到較高的效率。
如果會有多個執行緒共同存取字串的內容 , 就必須改用 StringBuffer 類別。
69
10-4 規則表示法 (Regular Expression)
在字串的使用上 , 有一種用途是接收使用者鍵入的資料 , 比如說身份證字號、電話號碼、或者是電子郵件帳號等等。
為了確保後續的處理正確 , 通常都會希望使用者依據特定的格式輸入 , 以避免使用者輸入不合乎該項資料的字元。
因此 , 在這類應用中 , 一旦取得使用者輸入的資料 ,
第一件事就是要判斷使用者是否依據規定的格式輸入 ,
然後才進行後續的處理。
70
規則表示法 (Regular Expression)
在 String 類別中 , 雖然已經提供有多個方法可以讓您比對字串的內容 , 可是要比對字串內容是否符合特定的格式 , 例如 02-28833498 這種電話號或是 joh
[email protected] 這樣的電子郵件信箱等等具有規則的樣式 , 使用起來並不方便。
因此我們需要一種可以描述字串內容規則的方式 , 然後依據此一規則來驗證字串的內容是否相符。
String 類別的 matches() 方法 , 就可以搭配規則表示法來解決這樣的問題。
71
10-4-1 甚麼是規則表示法
讓我們先以一個最簡單的範例來說明甚麼是規則表示法。
假設程式需要使用者輸入一個整數 , 那麼當取得使用者輸入的資料後 , 就必須檢查使用者所輸入的是否為整數。
要完成這件事 , 最簡單、直覺的方法就是使用一個迴圈 , 一一取出字串中的各個字元 , 檢查這個字元是否為數字。
75
比對數字
由於在標準萬國碼中 , 數字 '0' 、 ' 1' 、 '
2' 、 ..... 、 ' 9' 的字碼是連續的 , 因此只要比對字元是否位於 '0' 到 '9' 之間 , 即可確認該字元是否為數字。
76
甚麼是規則表示法
如果把第 16 行的迴圈用簡單的一句話來說 , 就是要檢查使用者所輸入的資料是否都是數字。
如果改用 String 類別的 matches() 方法搭配規則表示法 , 就可以更清楚的表達出比對的規則 , 底下就來修改前面程式的 do 迴圈。
78
甚麼是規則表示法
第 16 行就是使用 String 類別的 matches() 方法來檢查字串是否符合某種樣式 , 而 "[0- 9] +" 就是用來描述字串樣式的規則。 "[0-9]" 是指數字 0 ~ 9 之間的任意一個字元 , 而後面的 "+" 則是指前面規則所描述的樣式 ( 此例就是 [0-9]) 出現一次以上 , 所以整體來說 ,
這個規則就是『由一或多個數字所構成的字串』。 當字串本身符合所描述的樣式時 , matches() 就傳回 tr
ue, 否則傳回 false 。 因此 , 這個程式就和剛剛使用 for 迴圈檢查的功用一模一樣。
79
甚麼是規則表示法
從這裡您可以看到 , 使用 matches() 方法的好處是可以專注於要比對的樣式 , 至於如何比對 , 就交給 matches() 方法 , 而不需要自己撰寫程式進行。
因此 , 如果您需要比對的是這類可以規則化的樣式 ,
建議多多利用 matches() 方法。
85
限制出現次數
由於樣式是 "ab?a", 也就是先出現一個 'a', 再出現最多一個 'b', 再接著一個 'a', 所以 "aa" 或是 "ab
a" 都相符 , 但是 "abba" 中間出現了 2 個 'b ', 所以不相符。
86
字元種類 (Character Classes)
您也可以用中括號來表示一組字元 , 比如說:
其中樣式 [bjl] 表示此位置可以出現 'b' 或 'j' 或 'l', 因此 "a[bjl]a" 這個樣式的意思就是先出現一個 'a', 再出現一個 'b' 或 'j' 或 'l ', 再接著一個 'a' 。
在第 2 個執行結果中 , 因為輸入的字串第 2 個字元並非 'b' 或 'j' 或 'l', 所以不相符。
87
字元種類 (Character Classes)
您也可以在中括號中使用 "-" 表示一段連續的字碼區間 , 比如說上一小節使用過的 [0-9] 就包含了數字 , 而 [a-z] 則包含了小寫的英文字母 , [A-Z] 則包含了大寫的英文字母 , [a-zA-Z] 就是所有的英文字母了:
88
字元種類 (Character Classes)
這個範例的樣式表示先出現一個 ' a ' , 然後接著數字或是英文字母 , 再接著一個 'a', 所以第 2 個執行結果因為有 '#' 而不相符。
另外 , 您也可以在左中括號後面跟著一個 '^' , 排除中括號中的字元 , 例如:
90
預先定義的字元種類 (Character Class)
由於數字或是英文字母之類的規則很常會用到 ,
因此規則表示法中預先定義了一些字元種類 ,
如右所示:
▪ 由於句號代表任意字元 ,
原來的句號則需以 \.
表示。
93
群組 (Grouping)
其中以括號將 "c\dc" 組成群組 , 因此整個規則描述的樣式就是先出現一個 'a', 再出現 2 次 "c\dc",
再出現一個 'a' 。 第 1 個執行結果中的 "c1c" 以及 "c2c" 都符合 "c\dc" 樣式 , 而第 2 個執行結果只有 "c1c" 符合 "c\dc" 樣式 , 等於 "c\dc" 僅出現 1 次 , 所以比對不相符。
94
以字面常數指定樣式
如果要在程式中以字面常數指定樣式 , 由於 Java
的編譯器會將 '\' 視為跳脫序列的啟始字元 ( 例如 \
t 表定位字元、 \n 表換行字元、 \\ 代表 \ 字元 ),
因此要使用預先定義的字元種類時 , 就必須在前面多加一個 '\', 以便讓 Java 編譯器將 '\' 視為一般的字元 , 例如:
96
10-4-3 replaceAll ( ) 方法 規則表示法除了可以用來比對字串以外 , 也可以用來將字串中符合指定樣式的一段文字取代成另外一段文字 , 讓您可以極富彈性的方式進行字串的取代 , 而不是僅能使用簡單的 replace() 方法。
為了簡化 , 我們將剛剛的 RegExTest.java 修改 , 以方便測試 replaceAll() 方法:
99
簡單取代
replaceAll() 最簡單的用法就是當成 replace() 方法使用 , 以明確的字串內容當成樣式 , 並進行取代:
因為搜尋的樣式是 "111", 所以取代的結果就是將字串中的 "111" 取代掉。
100
使用樣式進行取代
replaceAll() 最大的用處是可以使用規則表示法 , 例如:
這裡搜尋的樣式是 "\d+", 所以字串中的 "111" 以及 "34" 都符合這個樣式 , 都會被取代為 " 數字 " 。
101
使用群組 有時候我們會希望取代的結果要包含原來被取代的那段文字 , 這時就可以使用群組的功能 , 例如:
其中要取代成 " 數字 $1" 中的 "$1" 的意思就是指比對相符的那段文字中 , 和樣式中第 1 個群組相符的部分。以本例來說 , 當 "111" 與 "(\d+)" 比對相符時 , 第一個群組就是 "(\d+)", 與這個群組相符的就是 "111" 這段文字 , 所以取代後的結果變成 " 數字 111" ;相同的道理 , 後面比對出 "34" 時 , 就取代為 " 數字 34" 了。
102
使用群組
依此類推 , $2 、 $3 、 ...自然是指第 2 、 3 、 .... 個群組了 , 至於 $0 則是指比對出的整段文字 , 例如:
規則表示法的功能非常強大 , 詳細的說明可以參考 JDK 的說明文件。
103
10-4-4 split( ) 方法
split() 方法可依照特定字元來切割字串 , 並將切割的結果存入字元陣列中傳回。
例如:『 "Ken,Sue,Tom".split(",")』就會以逗號來切割 , 然後傳回 {"Ken","Sue","Tom" } 陣列。
split() 有二種呼叫方法:
104
split( ) 方法
regex 代表一個規則表示法的樣式 , 而 limit 若為正數 , 則表示最多可切割的元素數目。
例如 『 "Ken,Sue,Tom".split(",", 2)』 會傳回只有 2 個元素的 {"Ken","Sue,Tom" } 陣列 , 也就是只會切割一次。
若 limit 為負數 , 即表示不限切割的數目。 若為 0, 則同樣不限切割的數目 , 而且最後面若有空字串的元素 , 也會自動移除;在呼叫 split() 時若省略第二個參數 , 則 limit 預設也是 0 。
108
1. Given:
What is the result?
A. x1,y23,z4, B. x,y,z, C. x,y,,z,
D. x,y,,z,, E. Compilation fails.
109
2. Given :
What is the result? A. x1,y23,z4, B. x,y,z, C. x,y,,z, D. x,y,,z,, E. Compilation fails..
110
3. Given :
Which codes, inserted at line 4, will print "love!"? (Choose all that apply.)A. b.delete(0, 2).replace(1,3,"o").append("!");
B. b.substring(2,6).insert(1, "o").replace(2,6,"ve!");
C. b.delete(6,7).delete(0,2).insert(1, "o").replace(2,6,"ve!");
D. b.insert(3, "o").replace(0,2,"").delete(2,4).replace(3,4,"e!");
E. b = "ove".insert(0,1,"l").append("!");
112
4. What 's the result of following program:
A. unm B. udm C. ude
D. Compilation fails. E. Runtime error.
113
5. Given:
Which statements are true? (Choose a l l that apply. )A. This code will have better performance if rewrited to: return
"<"+s.subString(0, n)+">";
B. This code will have better performance if replace StringBuffer to StringBuilder.
C. If replace StringBuffer to StringBuilder, no other change should be made.
D. This code is not thread-safe.
E. This code will be thread-unsafe if replace StringBuffer to StringBuilder.
115
6. Which statements concerning String, StringBuffer, and StringBuilder are true? (Choose all that apply.)A. All three classes have a substring() method.
B. All three classes have a length() method.
C. All three classes have overload append() method.
D. Only String class can use + operator.
E. Only StringBuffer class is thread-safe.
F. StringBuilder has better performance than StringBuffer.
G. The value of an object of those classes can be modified by their methods