讀古今文學網 > 精通正則表達式(第3版) > Matcher對像 >

Matcher對像

The Matcher Object

把正則表達式和目標字符串聯繫起來,生成Matcher對像之後,就可以以多種方式將其應用到目標字符串中,並查詢應用的結果。例如,對於給定的Matcher對像m,我們可以用m.find()來把m的表達式應用到目標字符串中,返回一個Boolean值,表示是否能找到匹配。如果能找到,m.group()返回實際匹配的字符串。

在講解Matcher的各種方法之前,不妨先瞭解瞭解它保存的各種信息。為了方便閱讀,下面的清單都提供了對應的詳細講解部分的頁碼。第一張清單中的元素是程序員能夠設置和更改的,而第二張清單中的元素是只讀的。

程序員能夠設置和修改的是:

●Pattern 對象,由程序員在創建 Matcher 時指定。可以通過 usePattern()方法更改(☞393)。當前所用的Pattern可以用pattern()方法獲得。

●目標字符串(或其他CharSequence對像),由程序員在創建Matcher時指定。可以通過reset(text)方法更改(☞392)。

●目標字符串的「檢索範圍(region)」(☞394)。默認情況下,檢索範圍就是整個目標字符串,但是程序員可以通過 region 方法,將其修改為目標字符串的某一段。這樣某些(而不是全部)匹配操作就只能在某個區域內進行。

當前檢索範圍的起始和結束偏移值可以通過 regionStart 和 regionEnd 方法獲得(☞386)。reset方法(☞392)會把檢索範圍重新設置為整個目標字符串,任何在內部調用reset方法的方法也是一樣。

●anchoring bounds標誌位。如果檢索範圍不等於整個目標字符串,程序員可以設定,是否將檢索範圍的邊界設置為「文本起始位置」和「文本結束位置」,這會影響文本行邊界元字符(\A^$\z\Z)。

默認情況下,這個標誌位為 true,但也可能改變,可以通過 useAnchoringBounds (☞388)和hasAnchoringBounds方法來修改和查詢。Reset方法不會修改標誌位。

●transparent bounds標誌位。如果檢索範圍是整個目標字符串中的一段文本,設置為true容許各種「考察(looking)」結構(順序環視、逆序環視,以及單詞分界符)超越檢索範圍的邊界,檢查外部的文本。

默認情況下,這個標誌位為false,但也可能改變,可以通過useTransparentBounds (☞388)和hasTran-sparentBounds方法來修改和查詢。Reset方法不會修改標誌位。

下面的只讀數據保存在matcher內部:

●當前pattern的捕獲型括號的數目可以通過groupCount(☞377)方法查詢。

●目標字符串中的match pointer或current location,用於支持「尋找下一個匹配」的操作(通過find方法☞375)。

●目標字符串中的append pointer,在查找-替換操作中(☞380),複製未匹配的文本部分時使用。

●表示到達字符串結尾的上一次匹配嘗試是否成功的標誌位。可以通過 hitEnd 方法(☞390)獲得這個標誌位的值。

●match result。如果最近一次匹配嘗試成功,Java會將各種數據收集起來,合稱為match result(☞376)。包括匹配文本的範圍(通過group()方法),匹配文本在目標字符串中的起始和結束偏移值(通過start()和end()方法),以及每一組捕獲型括號對應的信息(通過group(num)、start(num)和end(num)方法)。

match-result 數據封裝在 MatchResult 對像中,通過 toMatchResult 方法獲得。MatchResult方法有自己的group、start和end方法(☞377)。

●一個標誌位,表明更長的目標文本是否會導致匹配失敗(匹配成功之後才可用)。如果邊界元字符影響了匹配結果的生成,則此值為true。可以通過requireEnd方法查看它的值(☞390)。

上面列出的內容很多,但是如果按照功能分別講解,便很容易掌握。這正是下面幾節的內容。把這一章作為參考手冊時,本章開頭(☞366)的列表會很有用。

應用正則表達式

Applying the Regex

把matcher的正則表達式應用到目標文本時,主要會用到Matcher的這些方法:

boolean find

此方法在目標字符串的當前檢索範圍(☞384)中應用Matcher的正則表達式,返回的Boolean值表示是否能找到匹配。如果多次調用,則每次都在上次的匹配位置之後嘗試新的匹配。沒有給定參數的find只使用當前的檢索範圍(☞384)。

下面是簡單示例:

結果是:

match [Mastring]

如果這樣寫:

結果就是:

如果指定了整型參數,匹配嘗試會從距離目標字符串開頭offset個字符的位置開始。如果offset為負數或超出了目標字符串的長度,會拋出IndexOutOfBoundsException異常。

這種形式的find不會受當前檢索範圍的影響,而會把它設置為整個「目標字符串」(它會在內部調用reset方法☞392)。

第400頁的補充內容(作為第399頁問題的答案)給出了恰當的例子。

boolean matches

此方法返回的Boolean值表示matcher的正則表達式能否完全匹配目標字符串中當前檢索範圍的那段文本。也就是說,如果匹配成功,匹配的文本必須從檢索範圍的開頭開始,到檢索範圍的結尾結束(默認情況就是整個目標字符串)。如果檢索範圍設置為默認的「所有文本」,matches比使用「\A(?:…)\z」要簡單。

不過,如果當前檢索範圍不等於默認情況(☞384),使用matches可以在不受anchoring-bounds標誌位(☞388)影響的情況下檢查整個檢索範圍中的文本。

舉例來說,如果使用 CharBuffer 來保存程序中用戶輸入的文本,而檢索範圍設定為用戶用鼠標選定的部分。如果用戶點擊選區的部分,可以用m.usePattern(urlPattern).matches()來檢查選定部分是否為URL(如果是,則進行相應的處理)。

此方法返回的 Boolean 值表示 Matches 的正則表達式能否在當前目標字符串的當前檢索範圍中找到匹配。它類似於matches方法,但不要求檢索範圍中的整段文本都能匹配。

查詢匹配結果

Querying Match Results

下面列出的Matcher方法返回了成功匹配的信息。如果正則表達式還未應用過,或者匹配嘗試不成功,它們會拋出IllegalStateException。接收num參數(對應一組捕獲型括號)的方法,如果給定的num非法,會拋出IndexOutOfBoundsException。

請注意start和end方法,它們返回的偏移值不受檢索範圍的影響——偏移值從整個目標字符串的開頭開始計算,而不是檢索範圍的開頭。

後面還給出了一個例子,講解其中大部分方法的使用。

String group

返回前一次應用正則表達式的匹配文本。

int groupCount

返回正則表達式中與Matcher關聯的捕獲型括號的數目。在 group、start和 end方法中可以使用小於此數目的數字作為numth參數,下文介紹(注4)。

String gropu(int num)

返回編號為numth的捕獲型括號匹配的內容,如果對應的捕獲型括號沒有參與匹配,則返回null。如果numth為0,表示返回整個匹配的內容,group(0)就等於group()。

int start(int num)

此方法返回編號為 numth的捕獲型括號所匹配文本的起點在目標字符串中的絕對偏移值——即從目標字符串起始位置開始計算的偏移值。如果捕獲型括號沒有參與匹配,則返回-1。

int start

此方法返回整個匹配起點的絕對偏移值,start()就等於start(0)。

int end(int num)

此方法返回編號為 numth的捕獲型括號所匹配文本的終點在目標字符串中的絕對偏移值——即從目標字符串起始位置開始計算的偏移值。如果捕獲型括號沒有參與匹配,則返回-1。

int end

次方法返回整個匹配的終點的絕對偏移值,end()就等於end(0)。

MatcheResult toMatchResult

此方法從Java 1.5.0開始提供,返回的MatchResult對像封裝了當前匹配的信息。它和Matcher類一樣,也包含上面列出的group、start、end和groupCount方法。如果前一次匹配不成功,或者 Matcher 還沒有進行匹配操作,調用 toMatcheResult會拋出IllegalStateException。

示例

下面的例子示範了若干方法的使用。給定一個URL 字符串,這段代碼會找出URL 的協議名(『http』或是『https』)、主機名,以及可能出現的端口號:

執行的結果是:

簡單查找-替換

Simple Search and Replace

上面介紹的方法足夠進行查找-替換操作了,只是比較麻煩,但是 Matcher 提供了簡便的方法。

String replaceAll(String replacement)

返回目標字符串的副本,其中Matcher能夠匹配的文本都會被替換為replacement,具體處理過程在380頁。

此方法不受檢索範圍的影響(它會在內部調用reset方法),不過第382頁介紹了在檢索範圍中進行這樣操作的方法。

String類也提供了replaceAll的方法,所以:

string.replaceAll(regex,replacement)

就等於:

Pattern.compile(regex).matcher(string).replaceAll(replacement)

String replaceFirst(String replacement)

此方法類似replaceAll,但它只對第一次匹配(如果存在)進行替換。

String類也提供了replaceFirst方法。

static String quoteReplacement(String text)

此static方法從Java 1.5開始提供,返回text的文字用作replacement的參數。它為text副本中的特殊字符添加轉義,避免了下一頁講解的正則表達式特殊字符處理(下一節也給出了Matcher.quoteReplacement的例子)。

簡單查找-替換的例子

下面的程序將所有的「Java 1.5」改為「Java 5.0」,用市場化的名稱取代開發名稱:

結果是:

Before Java 5.0 was Java 1.4.2.After Java 5.0 is Java 1.6

如果pattern和matcher不需要復用,可以使用鏈式編程:

Pattern.compile(〞\\bJava\\s*1\\.5\\b〞).matcher(text).replaceAll(〞Java 5.0〞)

(如果單個線程中需要多次用到同一pattern,預編譯pattern對象可以提升效率☞372)

對正則表達式稍加修改,就可以用它來把「Java 1.6」改為「Java 6.0」(當然也需要修改replacement字符串,講解見下頁)。

Pattern.compile(〞\\bJava\\s*1\\.([56])\\b〞).matcher(text).replaceAll(〞Java$1.0〞)

對於同樣的輸入文本,結果如下:

Before Java 5.0 was Java 1.4.2.After Java 5.0 is Java 6.0

如果只需要替換第一次出現的文本,可以使用replaceFirst,而不是replaceAll。除了這種情況,還有一種情況可以使用replaceFirst,即明確知道目標字符串中只存在一個匹配時,使用 replaceFirst 可以提高效率(如果瞭解正則表達式或是目標字符串,可以預知這一點)。

replacement參數

replaceAll 和 replaceFirst 方法(以及下一節的 appendReplacement 方法)接收的replacement參數在插入到匹配結果之前,會進行特殊處理:

●『$1』、『$2』之類會替換為對應編號的捕獲型括號匹配的文本($0會被替換為所有匹配的文本)。

●如果『$』之後出現的不是ASCII的數字,會拋出IllegalArgumentException異常。

『$』之後的數字,只會應用「有意義」的部分。如果只有3組捕獲型括號,則『$25』會被視為$2然後是『5』,而此時$6會拋出IndexOutOfBoundsException。

●反斜線用來轉義後面的字符,所以『\$』表示美元符號。同樣的道理,『\\』表示反斜線(在Java的字符串中,表示正則表達式中的『\\』需要用四個斜線『\\\\』)。同樣,如果有12組捕獲型括號,而我們希望使用第一組捕獲型括號匹配的文本,然後是『2』,應該是這樣『$1\2』。

如果不清楚replacement字符串的內容,最好使用Matcher.quoteReplacement方法,確保不會出錯。如果用戶的正則表達式是uRegex,replacement是uRepl,下面的做法可以確保替換的安全:

Pattern.compile(uRegex).matcher(text).replaceAll(Matcher.quoteReplacement(uRepl))

高級查找-替換

Advanced Search and Replace

有兩個方法可以直接操作 Matcher 的查找-替換過程。它們配合起來,把結果存入用戶指定的 StringBuffer中。第一種方法每次匹配之後都會調用,在result中存入replacement字符串和匹配之間的文本。第二種方法會在所有匹配完成之後調用,將目標字符串中最後的文本拷貝過來。

Matcher appendReplacement(StringBuffer result,String replacement)

在正則表達式應用成功之後(通常是find)馬上調用此方法會把兩個字符串添加到指定的 result 中:第一個是原始目標字符串匹配之前的文本,然後是經過上面講解的特殊處理的replacement字符串。

舉例來說,如果matcher m與正則表達式「\w+」和『-->one+test<--』相聯繫,while循環的第一輪情況如下:

find找到下畫線標注的部分』。

然後,第一次appendReplacement調用會在StringBuffer result中加入匹配之前的文本『-->』,跳過匹配的文本,再插入replacement字符串『XXX』。

While循環的第二輪,find匹配。此時調用appendReplacement會給sb附加上匹配之前的文本『+』,然後仍然是字符串『XXX』。

這樣sb的值就是『-->XXX+XXX』,m在原始目標字符串中對應的位置是『-->one+test

<--』。現在該使用appendTail方法了,下文將會介紹。

StringBuffer appendTail(StringBuffer result)

找到所有匹配之後(或者是,找到期望的匹配之後——此時用戶可以停止匹配過程),這個方法將目標字符串中剩下的文本附加到提供的StringBuffer中。

在上例中,接下來就是:

m.appendTail(sb)

將『<--』附加到sb。這樣就得到『-->XXX+XXX<--』,查找-替換完成。

查找-替換示範

下面實現了一個自己的replaceAll的方法(並非必須,只是作為示範)。

它與 Java 內建的 replaceAll方法一樣,它不受檢索範圍(☞384)的影響,而是在查找-替換之前重置檢索範圍。

為了彌補這個缺憾,下面的replaceAll只在檢索範圍中進行,修改和新增的代碼會標注出來:

這裡使用「方法鏈(譯注1)」結合reset和region,詳細講解請參閱第389頁。下面的程序更加完善,它將metric變量中的攝氏溫度轉換為華氏溫度:

如果metric變量包含「from 36.3C to 40.1C.」,結果就是「from 97F to 104F.」。

原地查找-替換

In-Place Search and Replace

現在還只出現過對 String對像使用 java.util.regex的例子,但是Matcher其實適用於任何實現了CharSequence接口的類,所以我們能夠對目標文本進行實時的、原地(in place)的修改。

StringBuffer和 StringBuilder是兩種常用的實現了 CharSequence接口的類,前者保證線程安全性,但效率略低。這兩者都可以作為String來使用,但它們的值可以變化。本書中的例子使用了StringBuilder,但如果在多線程環境中,請使用StringBuffer。

這個簡單的例子在StringBuilder中搜索大寫單詞,將它們替換為小寫形式(注5):

結果是:

It's so very rude to shout!

其中匹配了兩次,調用了兩次text.replace。頭兩個參數指定需要替換的文本範圍(跳過表達式匹配的文本),然後是用作replacement的文本(也就是匹配文本的小寫形式)。

因為replacement和匹配文本長度相同,原地的查找-替換很簡單。如果不需要迭代進行查找-替換,這種方法非常方便。

長度變化的替換

如果replacement的長度不同於要替換文本的長度,情況就複雜起來。對目標字符串的修改是在「背後」進行的,所以「匹配指針(match pointer)」(在目標字符串中進行下一次find的開始位置)會發生錯誤。

要解決這個問題,我們可以自己維護匹配指針,明確告訴find下一次嘗試應該從何處開始。下面的例子做了這樣的改進,在需要插入的小寫文本兩端添加了<b>…</b>:

結果是:

It's <b>so</b> very <b>rude</b> to shout!

Matcher的檢索範圍

The Matcher's Region

從Java1.5 開始,Matcher 支持可變化的檢索範圍,通過它,我們可以把匹配嘗試限制在目標字符串之中的某個範圍。通常情況下,Matcher的檢索範圍包含整個目標字符串,但可以通過region方法實時修改。

下面的例子檢索 HTML 代碼字符串,報告不包含 ALT 屬性的 image tag。對同一段文本(HTML),它使用了兩個Matcher:一個尋找image tag,另一個尋找ALT屬性。

儘管兩個Matcher應用到同一個字符串,但它們的關聯只局限於,用找到的image tag來限定尋找ALT屬性的範圍。在調用ALT-matcher的find方法之前,我們用剛剛完成的image-tag的匹配來設定ALT-matcher的檢索範圍。

單拿出image tag的body之後,就可以通過查找ALT來判斷當前的tag內是否包含ALT屬性:

或許在一處指定目標字符串(創建 mAlt Matcher 時),另一處指定檢索範圍(調用mAlt.region時)的做法有些怪異。果真如此的話,我們可以為mAlt創建虛構的目標字符串(一個空字符串),然後每次都調用 mAlt.reset(html).region(…)。調用 reset可能會降低些效率,但是同時設定目標字符串和檢索範圍的邏輯更加清晰。

無論採取哪種辦法,都必須明白,如果不設定ALT Matcher的檢索範圍,對它調用find就會檢索整個目標字符串,返回無關的『ALT=』屬性。

下面繼續完善這個程序,返回找到的 image tag 在HTML 代碼中的行數。我們首先隔離出image tag之前的HTML代碼,然後計算其中的換行符數。

標注部分為新增代碼:

與之前一樣,每次設定ALT Matcher的檢索範圍時,都使用image matcher的start(1)方法得到image tag body在HTML中的起始位置。相反,在設定換行符匹配的檢索範圍終點時,使用start()方法來判斷整個image tag的開始位置(也就是換行符計算的終點)。

幾點提醒

記住,某些檢索相關的方法並非不受檢索範圍的影響,而是它們在內部調用了reset方法,把檢索範圍設定為默認的「全部文本」。

●受檢索範圍影響的查找方法:

●會重置matcher及其檢索範圍的方法:

另外請記住,匹配結果數據中的偏移值(也就是start和end方法返回的數值)是不受檢索範圍影響的,它們只與整個目標字符串的開始位置有關。

設定及查看檢索範圍的邊界

與設定及查看檢索範圍邊界的方法有3個:

Matcher region(int start,int end)

將matcher的檢索範圍設定在整個目標字符串的start和end之間,數值均從目標字符串的起始位置開始計算。它同樣會重置Matcher,將「匹配指針」指向檢索範圍的開頭,所以下一次調用find從此處開始。

如果沒有重新設定,或是調用reset方法(無論是顯式調用還是在其他方法內部調用☞392),檢索範圍都不會變化。

返回值為Matcher對像本身,所以此方法可用於方法鏈(☞389)。

如果start或end超出了目標字符串的範圍,或者start比end要大,會拋出IndexOutOf-BoundsException。

int regionStart

返回當前檢索範圍的起始位置在目標字符串中的偏移值,默認為0。

int regionEnd

返回當前檢索範圍的結束位置在目標字符串中的偏移值,默認為目標字符串的長度。因為region方法要求同時提供start和end,如果只希望設置其中一個,可能不太方便操作。表8-4給出了方法。

表8-4:設定檢索範圍的單個邊界

超越檢索範圍

如果將檢索範圍設定為整個目標字符串中的一段,正則引擎會忽略範圍之外的文本。也就是說,檢索範圍的起始位置可以用「^」匹配,而它可能並不是目標字符串的起始位置。

不過,某些情況下也可以檢查檢索範圍之外的文本。啟用transparent bounds能夠讓「考察(looking)」結構檢查範圍之外的文本,如果禁用anchoring bounds,則檢索範圍的邊界不會被視為輸入數據的起始/結束位置(除非實際情況如此)。

修改這兩個標誌位的理由與修改默認檢索範圍密切相關。之前用到了檢索範圍的例子都不需要設定這兩個標誌位——與檢索範圍相關的查找既不需要錨點也不需要「考察」結構。

如果程序把需要用戶編輯的文本存放在 CharBuffer中,用戶希望執行查找-替換操作,就應當把操作的範圍限定在光標之後的文本中,所以應當把檢索範圍的起始位置設定為當前光標所在的位置。如果用戶的光標指向下面的位置:

Madagas☞car is much too large to see on foot,so you'll need a car.

要求把「\bcar\b」替換為「automobile」。設定了檢索範圍之後(即將其設定為光標之後的文本),你可能會很驚奇地發現第一次匹配就是在檢索範圍的開頭:『Madagascar』。這是因為默認情況下transparent bounds標誌位設定為false,因此「\b」將檢索範圍起始位置設定為文本的起始位置,而「看不到」左側還有字符。如果將transparent bounds設定為true,「\b」就能看到『c』之前還有『s』,因此「\b」不能匹配。

Transparent Bounds

與這個標誌位相關的方法有兩個:

Matcher useTransparentBounds(boolean b)

設定transparent bounds的值。默認為false。

此方法返回Matcher本身,故可用在方法鏈中。

boolean hasTransparentBounds

如果transparent生效,則返回true。

Matcher 的transparent-bounds 默認為false。也就是說檢索範圍的邊界在順序環視、逆序環視和單詞分界符「考察」時是不透明的。這樣,正則引擎不會感知到檢索邊界之外的字符(注6)。

也就是說儘管檢索範圍的起始位置可能在某個單詞內部,「\b」仍然能夠匹配——它看不到之前的字母。

下面的例子說明了transparent bounds設置為false(默認)的情況:

結果是:也就是說儘管檢索範圍的起始位置可能在某個單詞內部,「\b」仍然能夠匹配——它看不到之前的字母。

Matches starting at character 7

單詞分界符的確匹配了檢索範圍的起始位置,即 Madagas☞car,儘管此處根本不是單詞的邊界。如果不設定transparent bounds,單詞分界符就「受騙(spoofed)」了。

如果在find之前添加這條語句:

m.useTransparentBounds(true);

結果就是:

Matches starting at character 27

因為邊界現在是透明的,引擎可以感知到起始邊界之前有個字母『s』,所以「\b」在此處無法匹配。於是結果就成了

同樣,transparent-bounds只有在檢索範圍不等於「整個目標字符串」時才有意義。即使reset方法也不會重置它。

Anchoring bounds

與anchoring bounds有關的方法有:

Matcher useAnchoringBounds(Boolean b)

設置matcher的anchoring-bounds的值,默認為true。

此方法會返回matcher對像本身,故可用於方法鏈中。

boolean hasAnchoringBounds

如果啟用了anchoring bounds,則返回true,否則返回false。

默認狀態下,anchoring bounds為true,也就是說行錨點(^\A $\z\Z)能匹配檢索範圍的邊界,即檢索範圍不等於整個目標字符串。將它們設置為false表示行錨點只能匹配檢索範圍內,整個目標字符串中符合規定的位置。

禁用anchoring bounds的理由可能與使用transparent bounds一樣,當用戶的「光標不在整段文本的起始位置時」保證語意的完備。

與transparent-bounds一樣,anchoring bounds也只有在檢索範圍不等於「整個目標字符串」時才有意義。即使reset方法也不會重置它。

方法鏈

Method Chaining

下面的程序初始化一個Matcher,並設定某些選項:

在前面的例子中我們看到,如果創建Matcher之後不再需要regex,可以把前面兩步合併起來:

不過,因為Matcher的兩個方法會返回Matcher本身,可以把它們整合成一行(儘管因為排版的原因必須列為兩行):

功能並沒有增加,但用起來更加簡便。這種「方法鏈」格式緊湊,一行程序可能很難對應到單個步驟的文檔,不過,好的文檔重在說明「為什麼」而不是「幹什麼」,所以這可能並不是個問題。在第399頁的程序中,使用方法鏈可以保證格式緊湊清晰。

構建掃瞄程序

Methods for Building a Scanner

Java 1.5的matcher提供了兩個新方法,hitEnd和requireEnd,它們主要用來構建掃瞄程序(Scanner)。掃瞄程序將字符流解析為記號(token)流。舉例來說,編譯器的掃瞄程序會把『var·<·34』解析為三個記號:INDENTIFIER·LESS_THAN·INTEGER。

這兩個方法幫助掃瞄程序決定,剛剛完成的匹配嘗試的結果是否應該用來解釋(interpretation)當前輸入。一般來說,只要其中一個返回 true,就表示在做出決定之前還應該輸入更多的數據。例如,如果當前的輸入數據(比如用戶在交互式調試器中輸入的命令)是單個字符『<』,最好還是看看下一個字符是否『=』,才能決定這個記號是LESS_THAN還是LESS_THAN_OR_EQUAL。

在大多數應用正則表達式的項目中,這兩個方法可能派不上用場,可是一旦需要,它們就是不可替代的。在這些不常見的場合,Java 1.5中hitEnd方法存在的bug就很讓人惱火。不過,看起來Java 1.6已經修正了這個錯誤,Java 1.5中的解決辦法將在本章末尾介紹。

構建掃瞄程序已經遠遠偏離了本書的主旨,所以我只會介紹些具體方法的定義,並給出例子(如果你確實需要掃瞄程序,應該去看看java.util.Scanner)。

boolean hitEnd

(Java 1.5中這個方法是不可靠的,解決辦法參見第392頁)。

此方法表示,正則引擎在上一次匹配嘗試中,是否檢查了輸入數據結束之後的數據(而無論上一次匹配是否成功)。其中包含「\b」和「$」之類的邊界檢查。

如果hitEnd返回true,則輸入更多數據可能會改變匹配結果(匹配成功變為匹配失敗,匹配失敗變為匹配成功,或者匹配文本發生變化)。相反,如果返回false,則匹配結果已經確定,輸入更多的數據也不會改變匹配結果。

常見的應用是,如果匹配成功,而hitEnd返回true,則必須等待更多的輸入數據才能最後做出決定。如果匹配失敗,而 hitEnd返回 false,應該期待更多的輸入數據,而不是報告語法錯誤。

boolean requireEnd

此方法只有在匹配成功之後才有意義,它表示正則引擎的匹配成功與否是否受輸入數據結尾的影響。也就是說,如果requireEnd返回true,更多的輸入數據可能導致匹配嘗試失敗,如果返回false,更多的輸入數據可能改變匹配的細節,但不會導致匹配失敗。

常見的應用是,如果requireEnd返回true,在最後做出決定之前,必須接受更多的輸入數據。

hitEnd和requireEnd都受到檢索範圍的影響。

使用hitEnd和requireEnd的例子

表8-5給出了在lookingAt搜索之後使用hitEnd和requireEnd的例子。所給的兩個例子雖然很簡單,但足夠解釋這兩個方法。

表8-5:在lookingAt搜索之後使用hitEnd和requireEnd的例子

表8-5中上面7行的正則表達式尋找一個非負整數以及4個比較運算符:大於、大於等於、小於、小於等於。下面8行的正則表達式更簡單,尋找單詞 set或是 setup。這些例子很簡單,但能說明問題。

舉例來說,第5行中,雖然整個目標字符串都能匹配,hitEnd仍然會返回false。原因在於,儘管匹配文本包含目標字符串的最後一個字符,引擎也不需要檢查之後的字符(無論是字符還是分界符)。

hitEnd的bug及解決辦法

Java 1.5中的hitEnd方法存在bug(Java 1.6已經修正)(注7),在某些特殊情況下hitEnd會得到不可靠的結果:在不區分大小寫的匹配模式下,如果正則表達式的某個可選元素為單個字符(尤其是當它的匹配嘗試失敗時),就會出錯。

例如,在不區分大小寫的情況下使用「>=?」(它作為大的正則表達式的一部分)會誘發這個錯誤,因為『=』是可選的單個字符。在不區分大小寫的情況下使用「a|an|the」(仍然是包含在大的正則表達式中)也會誘發這個錯誤,因為單個字符「a」是眾多多選分支之一,因此是可選的。

另兩個例子是「values?」和「\r?\n\r?\n」。

解決辦法 解決的辦法是破壞誘發條件,或者禁用不區分大小寫的匹配(至少是對誘發的子表達式禁用),或者是把單個字符替換為其他元素,例如字符組。

第一種辦法會把「>=?」替換為「(?-i:>=?)」,使用模式修飾範圍(☞110)保證不區分大小寫的匹配不會應用於這個子表達式(這裡不存在大小寫的區別,所以這種辦法完全沒問題)。

如果使用第二種辦法,「a|an|the」就變成了「[aA]|an|the」,代表了使用 Pattern.CASE_INSENSITIVE進行不區分大小寫匹配的情況。

Matcher的其他方法

Other Matcher Methods

這些Matcher方法尚未介紹過:

Matcher reset

這個方法會重新初始化 Matcher 的大多數信息,棄用前一次成功匹配的所有信息,將匹配位置指向文本的開頭,把檢索範圍(☞384)恢復為默認的「全部文本」。只有anchoring bounds和transparent bounds(☞388)不會變化。

Matcher有三個方法會在內部調用reset,因此也會重新設定檢索範圍:replaceAll、replaceFirst,以及只使用一個參數的find。

這個方法返回Matcher本身,所以它可以用在方法鏈中(☞389)。

Matcher reset(CharSequence text)

這個方法與 reset()差不多,但還會把目標文本改為新的 String(或者任何實現CharSequence的對象)。

如果你希望對多個文本應用同樣的正則表達式(例如,對所讀入的文件的每一行),使用reset方法比多次創建新的Matcher更有效率。

這個方法返回Matcher本身,所以可以用在方法鏈中(☞389)。

Pattern pattern

Matcher的pattern方法返回與此Matcher關聯的Pattern對象。如果希望觀察所使用的正則表達式,請使用 m.pattern().pattern(),它會調用 Pattern 對像(名字相同,但對像不同)的pattern方法(☞394)。

Matcher usePattern(Pattern p)

從Java 1.5開始添加,這個方法會用給定的Pattern對像替換當前與Matcher關聯的Pattern對象。這個方法不會重置Matcher,所以能夠在文本的「當前位置」開始使用不同的pattern。第399頁有此方法實際應用的例子和討論。

這個方法返回Matcher本身,所以可以用在方法鏈中(☞389)。

String toString

從Java 1.5中添加,這個方法返回包含Matcher基本信息的字符串,調試時這很有用。字符串的內容可能會變化,在Java 1.6 beta中是這樣:

結果是:

Java 1.4.2的Matcher類只有繼承自java.lang.Object的toString方法,它返回沒什麼信息含量的字符串:『java.util.regex.Matcher@480457』。

查詢Matcher的目標字符串

Matcher類沒有提供查詢當前目標字符串的方法,但有些辦法繞過了這種限制:

這裡使用replaceFirst方法,以及虛構的pattern和replacement字符串,來取得目標字符串的未經修改的副本。其中它重置了Matcher,但也恢復了之前的檢索範圍。它不是特別好的解決方案(效率也不是很高,而且即便 Matcher 的目標字符串可能是其他類型也會返回String),但是在Sun給出更好的辦法之前,它還湊合。