剖析、驗證規則表示式往往是最耗時間的階段,在頻繁使用某規則表示式的場合,若可以將剖析、驗證過後的規則表示式重複使用,對效率將會有所幫助。
java.util.regex.Pattern
實例是規則表示式在 JVM 中的代表物件,Pattern
的建構式被標示為 private
,無法用 new
建構 Pattern
實例,必須透過 Pattern
的靜態方法 compile
來建構,在剖析、驗證過規則表示式無誤後,將會傳回 Pattern
實例,之後就可以重複使用這個實例。例如:
Pattern pattern = Pattern.compile(".*foo");
Pattern.compile
方法的另一版本,可以指定旗標,例如想不分大小寫比對 dog 文字,可以如下:
Pattern pattern = Pattern.compile("dog", Pattern.CASE_INSENSITIVE);
也可以在規則表示式中使用嵌入旗標表示法(Embedded Flag Expression)。例如 Pattern.CASE_INSENSITIVE
等效的嵌入旗標表示法為 (?i)
,以下片段效果等同上例:
Pattern pattern = Pattern.compile("(?i)dog");
若想對特定分組嵌入旗標,可以使用 (?i:dog)
這樣的語法;並非全部的常數旗標都有對應的嵌入式表示法,底下列出有對應的旗標:
Pattern.CASE_INSENSITIVE
:(?i)
Pattern.COMMENTS
:(?x)
Pattern.MULTILINE
:(?m)
Pattern.DOTALL
:(?s)
Pattern.UNICODE_CASE
:(?u)
Pattern.UNICODE_CHARACTER_CLASS
:(?U)
Pattern.UNIX_LINES
:(?d)
Pattern.CANON_EQ
與 Pattern.LITERAL
沒有對應的嵌入式表示法。Pattern.CANON_EQ
會啟用 Canonical equivalence,簡單來說,像 å 字元(U+00E5),也會使用 a
與 ̊ 組合標示(combining mark)(U+030A)來表示,對 Pattern
來說,預設兩個是不等價的,然而啟用了 Pattern.CANON_EQ
,兩者會視為相同:
jshell> var regex = Pattern.compile("a\u030A");
regex ==> a?
jshell> regex.matcher("\u00E5").find();
$2 ==> false
jshell> var regex2 = Pattern.compile("a\u030A", Pattern.CANON_EQ);
regex2 ==> a?
jshell> regex2.matcher("\u00E5").find();
$4 ==> true
注意到,"a\u030A"
與 "a\\u030A"
的差別,前者的 \u030A
是字串表示,後者是規則表示式。
如果規則表示式寫 a\u030A
,就真的是比對 a
之後有個碼點為 030A 的字元(而不是被當成組合標示):
var regex2 = Pattern.compile("a\\u030A", Pattern.CANON_EQ);
out.println(regex2.matcher("\u00E5").find()); // 顯示 false
如果你使用 .
來比對 a
與 ̊ 組合,結果會是 false
,在 Java 9 中,新增了一個 \X
,可用來直接比對這類具有組合標示的字元:
jshell> "a\u030A".matches(".");
$5 ==> false
jshell> "a\u030A".matches("\\X");
$6 ==> true
可以使用 matcher
方法指定要比對的字串,這會傳回 java.util.regex.Matcher
實例,表示對指定字串的比對器,可以使用 find
方法看看是不是有下一個符合字串,下一篇文件會再細談 Matcher
。
Pattern.LITERAL
的話,在〈String 與 Regex〉中看過,可用來將全部的詮譯字元當成一般字元來比對。
在設定 Pattern.CASE_INSENSITIVE
時,可以加上 Pattern.UNICODE_CASE
啟用 Unicode 版本的忽略大小寫。例如,比較 Ä 與 ä:
jshell> var regex3 = Pattern.compile("\u00C4", Pattern.CASE_INSENSITIVE);
regex3 ==> ?
jshell> regex3.matcher("\u00E4").find();
$8 ==> false
jshell> var regex4 = Pattern.compile("\u00C4", Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE);
regex4 ==> ?
jshell> regex4.matcher("\u00E4").find();
$10 ==> true
Pattern.UNICODE_CHARACTER_CLASS
在〈特性類〉中談過,預定義與 POSIX 字元類,可以藉由設置此旗標,令其與對應的 Unicode 特性具有一致的表示。
規則表示式本身可讀性差、除錯不易,如果因規則表示式有誤而導致 compile
呼叫失敗,會拋出 java.util.regex.PatternSyntaxException
,可以使用 getDescription
取得錯誤說明,使用 getIndex
取得錯誤索引,使用 getPattern
取得錯誤的規則表示式,getMessage
會以多行顯示錯誤的索引、描述等綜合訊息。
在取得 Pattern
實例後,可以使用 split
方法將指定字串依規則表示式切割,效果等同於使用 String
的 split
方法:
jshell> var regex = Pattern.compile("\\+");
regex ==> \+
jshell> regex.split("Justin+Monica+Irene");
$12 ==> String[3] { "Justin", "Monica", "Irene" }
由於 Java 8 之後支援 Stream API,Pattern
也因應而新增了 splitAsStream
靜態方法,它傳回的是 Stream<String>
,適用於需要管線化、惰性操作的場合:
jshell> var regex = Pattern.compile("\\+");
regex ==> \+
jshell> var tokens = regex.splitAsStream("Justin+Monica+Irene");
tokens ==> java.util.stream.ReferencePipeline$Head@5a1c0542
jshell> tokens.filter(token -> token.indexOf('i') != -1).map(String::toUpperCase).forEach(out::println);
JUSTIN
MONICA
Pattern
實例可以藉由 asPredicate
轉為 Predicate
物件,在需要 Predicate
作為引數的場合時可以使用:
jshell> var regex = Pattern.compile("\\+");
regex ==> \+
jshell> var tokens = regex.splitAsStream("Justin+Monica+irene");
tokens ==> java.util.stream.ReferencePipeline$Head@2833cc44
jshell> tokens.filter(Pattern.compile("\\p{Upper}+").asPredicate()).forEach(System.out::println);
Justin
Monica