String.replaceAll與java.lang.IllegalArgumentException

來源:互聯網
上載者:User

                String.replaceAll與java.lang.IllegalArgumentException
0.應用情境(非真實應用,筆者設計)
在進行貨幣的轉換時,需要將貨幣的單位根據不同國家的貨幣符號進行替換,例如:
中國:¥
美國:$
英國:£
此時表示貨幣單位的欄位使用一個變數來表示,例如{curr}。
根據不同的地區設定,將變數替換為對應的貨幣符號。
1.CODE
筆者寫了一個很簡單的類,來實現這個功能,最初的代碼如下:
package org.ly;</p><p>import java.text.DecimalFormatSymbols;<br />import java.util.Locale;</p><p>public class StringReplace {</p><p> public static void main(String[] args) {<br /> String price = "{currency.symbol}1000.00";<br /> DecimalFormatSymbols dfs = new DecimalFormatSymbols(Locale.CHINA);<br /> System.out.println("The price is " + price.replaceAll("{currency.symbol}", dfs.getCurrencySymbol()));<br /> }<br />}</p><p>
2.問題一
執行上面的類,執行結果如下:
java.util.regex.PatternSyntaxException: Illegal repetition<br />{currency.symbol}<br /> at java.util.regex.Pattern.error(Pattern.java:1541)<br /> at java.util.regex.Pattern.closure(Pattern.java:2558)<br /> at java.util.regex.Pattern.sequence(Pattern.java:1669)<br /> at java.util.regex.Pattern.expr(Pattern.java:1558)<br /> at java.util.regex.Pattern.compile(Pattern.java:1291)<br /> at java.util.regex.Pattern.<init>(Pattern.java:1047)<br /> at java.util.regex.Pattern.compile(Pattern.java:785)<br /> at java.lang.String.replaceAll(String.java:1663)<br /> at atis.StringReplace.main(StringReplace.java:13)<br />Exception in thread "main"
當出現異常或者錯誤時,解決方案有兩種:
a.外事問Google,內事問Baidu
Google搜尋了一下,原來{和}是Regex中的關鍵字,所以需要轉義。
b.自己動手,Debug一下
在java.util.regex.Pattern.closure方法的2558行(筆者使用的JDK為1.4.2_15),有如下的代碼:
case '{':<br /> ch = temp[cursor+1];<br /> if (ASCII.isDigit(ch)) {<br /> ......<br /> } else {<br /> error("Illegal repetition");<br /> }
即,如果出現{符號,並且後面的字元不是數字,那麼就會拋出異常。
在JDK的文檔中,對於Regex的關鍵字也有如下的說明:
Greedy quantifiers
X? - X, once or not at all
X* - X, zero or more times
X+ - X, one or more times
X{n} - X, exactly n times
X{n,} - X, at least n times
X{n,m} - X, at least n but not more than m times
 
Reluctant quantifiers
X?? - X, once or not at all
X*? - X, zero or more times
X+? - X, one or more times
X{n}? - X, exactly n times
X{n,}? - X, at least n times
X{n,m}? - X, at least n but not more than m times
 
Possessive quantifiers
X?+ - X, once or not at all
X*+ - X, zero or more times
X++ - X, one or more times
X{n}+ - X, exactly n times
X{n,}+ - X, at least n times
X{n,m}+ - X, at least n but not more than m times

解決方案:將{和}進行轉義,將上面的類中的replaceAll的代碼進行如下的修改:
System.out.println("The price is " + price.replaceAll("//{currency.symbol//}", dfs.getCurrencySymbol()));
此時啟動並執行結果如下:
The price is ¥1000.00

3.問題二
上面的修改可以使程式正常運行,但是要使程式能夠完全正常的運行,應該通過所有的測試案例。
下面將Locale設定為美國。此時我們想要輸出的結果應該為:
The price is $1000.00
修改類的代碼:
DecimalFormatSymbols dfs = new DecimalFormatSymbols(Locale.CHINA);
==>
DecimalFormatSymbols dfs = new DecimalFormatSymbols(Locale.US);
運行程式,得到如下的輸出:
java.lang.StringIndexOutOfBoundsException: String index out of range: 1<br /> at java.lang.String.charAt(String.java:444)<br /> at java.util.regex.Matcher.appendReplacement(Matcher.java:559)<br /> at java.util.regex.Matcher.replaceAll(Matcher.java:661)<br /> at java.lang.String.replaceAll(String.java:1663)<br /> at atis.StringReplace.main(StringReplace.java:13)<br />Exception in thread "main"

這次通過使用Debug的方式(筆者偏向於這種方式,很多時候發現問題,自己找出並解決可以避免下次發生類似的錯誤,並且印象比較深刻,當然是在不花費太多時間為前提),可以發現問題出在:
java.util.regex.Matcher.appendReplacement(Matcher.java:559)<br />while (cursor < replacement.length()) {<br /> char nextChar = replacement.charAt(cursor);<br /> if (nextChar == '//') {<br /> cursor++;<br /> nextChar = replacement.charAt(cursor);<br /> result.append(nextChar);<br /> cursor++;<br /> } else if (nextChar == '$') {<br /> // Skip past $<br /> cursor++;</p><p> // The first number is always a group<br /> int refNum = (int)replacement.charAt(cursor) - '0';<br /> if ((refNum < 0)||(refNum > 9))<br /> throw new IllegalArgumentException(<br /> "Illegal group reference");<br /> cursor++;</p><p>
這裡字元/和$都是作為一種特殊情況來處理的,例子程式中替換的字串為$,此時會使遊標自增,而替換的字串的長度為1,導致了ArrayIndexOutOfBoundsException。
而如果我們將替換字元修改為"$a",那麼出現的異常變成了另外一種:
java.lang.IllegalArgumentException: Illegal group reference
問題發生的地方同樣,當字元為$符號時,遊標自增1,然後如果$的下一個字元的ASCII不是0~9之間,就會拋出上述異常。

究其原因,仍然是因為$也是Regex中的特殊符號,在JDK文檔中的說明如下:
Boundary matchers
^ The beginning of a line
$ The end of a line

解決方案:對$字元進行轉義。轉義後即可正確的輸出。

3.Better解決方案
眾所周知,在屬性檔案中經常會使用{0},{1}這樣的參數來進行運行時的訊息替換。例如:
errorCode = The input age value {0} is invalid. Age value must be a integer.
這也是一種典型的Struts訊息方式。
Struts是如何進行的替換呢,原來Struts使用了JDK提供的基礎類java.text.MessageFormat。
MessageFormat可以通過傳入一個String參數(訊息)進行構造,然後通過成員方法format(Object)來進行格式化,這裡Object可以傳入字串,字串數組等。

這樣,上面的類可以修改為:
package atis;</p><p>import java.text.DecimalFormatSymbols;<br />import java.text.MessageFormat;<br />import java.util.Locale;</p><p>public class StringReplace {</p><p> public static void main(String[] args) {<br /> String price = "The price is {0}1000.00";<br /> MessageFormat format = new MessageFormat(price);<br /> DecimalFormatSymbols dfs = new DecimalFormatSymbols(Locale.US);<br /> System.out.println(format.format(new String[] { dfs.getCurrencySymbol() }));<br /> }<br />}<br /> 4.總結
本文主要介紹了在使用String.replaceAll時容易出現的兩個問題,即String.replaceAll內部使用了Regex,對Regex的關鍵字需要進行格外小心的處理。
同時介紹了java.text.MessageFormat類如何進行訊息中參數的替換。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.