Java 正則表達式核心 API 快速上手
Java 正則表達式處理主要依賴 java.util.regex 套件中的兩個核心類別:Pattern(編譯後的正則模式)與 Matcher(針對輸入字串執行匹配的引擎)。掌握其標準用法與簡寫用法,能大幅提升字串解析、驗證、替換等任務的開發效率。
一、Pattern 與 Matcher 標準使用流程
1. 典型三步驟用法
// 1. 將正則字串編譯為 Pattern 物件(可重複使用、執行緒安全)
Pattern p = Pattern.compile("a*b");
// 2. 使用 Pattern 建立 Matcher 物件,綁定待匹配字串
Matcher m = p.matcher("aaaaab");
// 3. 執行匹配操作
boolean b = m.matches(); // 返回 true,整個字串符合 "a*b" 模式
2. 單次使用簡寫:Pattern.matches()
若正則表達式僅需使用一次,可直接呼叫靜態方法,自動完成編譯與匹配:
boolean b = Pattern.matches("a*b", "aaaaab"); // 返回 true
3. 關鍵特性提醒
- Pattern 是不可變類別,編譯後可安全地在多執行緒間共用。
- Matcher 非執行緒安全,每個執行緒應建立自己的實例。
二、Matcher 常用方法速查
| 方法 | 說明 | 回傳值範例 |
matches() | 嘗試將整個目標字串與模式匹配 | true / false |
find() | 掃描目標字串,尋找下一個符合模式的子串 | true / false |
group() | 返回上一次成功匹配的子串(整組匹配) | 匹配到的字串 |
group(int group) | 返回指定捕獲組的內容(group 0 為整組) | 捕獲組字串 |
start() | 返回上一次匹配子串在目標字串中的起始索引 | int 索引值 |
end() | 返回上一次匹配子串的結束索引 + 1 | int 索引值 |
replaceAll(String replacement) | 將所有匹配子串替換為指定字串 | 新字串 |
replaceFirst(String replacement) | 僅替換第一個匹配子串 | 新字串 |
實戰範例:提取所有數字
String text = "訂單號:20231015,金額:3580 元,會員 ID:VIP888";
Pattern p = Pattern.compile("\\d+");
Matcher m = p.matcher(text);
while (m.find()) {
System.out.printf("找到:%s 位置[%d, %d)%n", m.group(), m.start(), m.end());
}
/* 輸出:
找到:20231015 位置[5, 13)
找到:3580 位置[17, 21)
找到:888 位置[30, 33)
*/
三、正則表達式核心語法速查表
以下整理最常用的元字元、量詞、字元類別、邊界與分組語法,建議收藏備查。
1. 字元類別
| 語法 | 說明 | 範例匹配 |
. | 任意單一字元(除換行符) | "a", "1", "@" |
\d | 數字 [0-9] | "5" |
\D | 非數字 [^0-9] | "A" |
\w | 單字字元 [a-zA-Z0-9_] | "z", "9", "_" |
\W | 非單字字元 | "@", "#" |
\s | 空白字元(空格、Tab、換行) | " " |
\S | 非空白字元 | "x" |
[abc] | 字元集合:a 或 b 或 c | "a" |
[^abc] | 排除字元集合 | "d" |
[a-z] | 字元範圍 | "m" |
2. 量詞(貪婪/懶惰/佔有)
| 貪婪 | 懶惰 | 佔有 | 說明 |
* | *? | *+ | 0 次或多次 |
+ | +? | ++ | 1 次或多次 |
? | ?? | ?+ | 0 次或 1 次 |
{n} | {n}? | {n}+ | 恰好 n 次 |
{n,} | {n,}? | {n,}+ | 至少 n 次 |
{n,m} | {n,m}? | {n,m}+ | n 到 m 次 |
提示:預設為貪婪模式(盡可能多匹配),在量詞後加 ? 切換為懶惰模式(盡可能少匹配),加 + 為佔有模式(不回溯,效能最佳但功能受限)。
3. 邊界與鍵定
| 語法 | 說明 |
^ | 行首(Pattern.MULTILINE 開啟時為每行行首) |
$ | 行尾(Pattern.MULTILINE 開啟時為每行行尾) |
\b | 單字邊界(字母/數字/底線與非單字字元之間) |
\B | 非單字邊界 |
\A | 字串開頭(不受 MULTILINE 影響) |
\z | 字串結尾(不受 MULTILINE 影響) |
\Z | 字串結尾或結尾換行符之前 |
4. 分組與反向引用
| 語法 | 說明 | 範例 |
(...) | 捕獲組,可用 group(1) 等取得 | (\d{4})-(\d{2}) 擷取年、月 |
(?:...) | 非捕獲組,僅分組不佔用 group 索引 | (?:\d{3})?\d{3}-\d{4} |
(?<name>...) | 具名捕獲組(Java 7+),group("name") 取得 | (?<year>\d{4}) |
\1, \2... | 正則內部反向引用前面捕獲組 | (['\"]).*?\1 匹配成對引號 |
$1, $2... | 替換字串中引用捕獲組 | m.replaceAll("$2-$1") |
5. 斷言(零寬斷言)
| 語法 | 名稱 | 說明 |
(?=...) | 正向先行斷言 | 後面必須匹配 ...,不消耗字元 |
(?!...) | 負向先行斷言 | 後面不能匹配 ... |
(?<=...) | 正向後行斷言 | 前面必須匹配 ...(固定長度) |
(?<!...) | 負向後行斷言 | 前面不能匹配 ...(固定長度) |
6. 常用 Flag(Pattern.compile(regex, flags))
Pattern.CASE_INSENSITIVE (?i):忽略大小寫
Pattern.MULTILINE (?m):^/$ 匹配每行首尾
Pattern.DOTALL (?s):. 匹配含換行符的任意字元
Pattern.COMMENTS (?x):允許註釋與空白,提升可讀性
Pattern.UNICODE_CASE (?u):配合 CASE_INSENSITIVE 進行 Unicode 標準大小寫折疊
四、進階實戰技巧
1. 字串分割:String.split() 底層即使用 Pattern
String[] parts = "a,b;;c , d".split("\\s*[;,]\\s*");
// 結果:["a", "b", "c", "d"]
2. 驗證完整字串格式(手機號、Email 等)
// 手機號簡易驗證(中國大陸)
String mobileRegex = "^1[3-9]\\d{9}$";
boolean valid = Pattern.matches(mobileRegex, "13812345678"); // true
3. 替換時引用捕獲組:格式化日期
String raw = "20231015";
String formatted = raw.replaceAll("(\\d{4})(\\d{2})(\\d{2})", "$1-$2-$3");
// 結果:"2023-10-15"
4. 具名捕獲組提升可維護性(Java 9+ 建議)
Pattern p = Pattern.compile("(?<year>\\d{4})-(?<month>\\d{2})-(?<day>\\d{2})");
Matcher m = p.matcher("2023-10-15");
if (m.matches()) {
System.out.println(m.group("year")); // 2023
System.out.println(m.group("month")); // 10
System.out.println(m.group("day")); // 15
}
五、效能與最佳實踐建議
- 預編譯 Pattern 重複使用,避免在迴圈或高頻呼叫中重複
Pattern.compile()。
- 優先使用具體字元類別(如
[a-z])而非 .,減少回溯。
- 避免巢狀量詞(如
(a*)*),極易導致災難性回溯(ReDoS)。
- 長字串匹配使用
find() 迴圈,而非 matches()。
- 針對固定前綴/後綴的模式,加上
^/$ 或 \A/\z 讓引擎快速失敗。
- 複雜正則拆解為多個簡單步驟,或使用
Pattern.COMMENTS 加上註釋提升可讀性。
掌握 Java 正則表達式的關鍵在於理解 Pattern 編譯與 Matcher 狀態機 的運作模式,熟練運用分組、斷言、旗標等進階語法,並養成「預編譯、避免回溯、善用捕獲組」的工程習慣。將本文語法表加入書籤,下次遇到字串解析難題時即可快速查閱、精準出擊。