String.replaceAll与java.lang.IllegalArgumentException

                String.replaceAll与java.lang.IllegalArgumentException
0.应用场景(非真实应用,笔者设计)
在进行货币的转换时,需要将货币的单位根据不同国家的货币符号进行替换,例如:
中国:¥
美国:$
英国:£
此时表示货币单位的字段使用一个变量来表示,例如{curr}。
根据不同的区域设置,将变量替换为对应的货币符号。
1.CODE
笔者写了一个很简单的类,来实现这个功能,最初的代码如下:
package org.ly; import java.text.DecimalFormatSymbols; import java.util.Locale; public class StringReplace { public static void main(String[] args) { String price = "{currency.symbol}1000.00"; DecimalFormatSymbols dfs = new DecimalFormatSymbols(Locale.CHINA); System.out.println("The price is " + price.replaceAll("{currency.symbol}", dfs.getCurrencySymbol())); } }
2.问题一
执行上面的类,执行结果如下:
java.util.regex.PatternSyntaxException: Illegal repetition {currency.symbol} at java.util.regex.Pattern.error(Pattern.java:1541) at java.util.regex.Pattern.closure(Pattern.java:2558) at java.util.regex.Pattern.sequence(Pattern.java:1669) at java.util.regex.Pattern.expr(Pattern.java:1558) at java.util.regex.Pattern.compile(Pattern.java:1291) at java.util.regex.Pattern.(Pattern.java:1047) at java.util.regex.Pattern.compile(Pattern.java:785) at java.lang.String.replaceAll(String.java:1663) at atis.StringReplace.main(StringReplace.java:13) Exception in thread "main"
当出现异常或者错误时,解决方案有两种:
a.外事问Google,内事问Baidu
Google搜索了一下,原来{和}是正则表达式中的关键字,所以需要转义。
b.自己动手,Debug一下
在java.util.regex.Pattern.closure方法的2558行(笔者使用的JDK为1.4.2_15),有如下的代码:
case '{': ch = temp[cursor+1]; if (ASCII.isDigit(ch)) { ...... } else { error("Illegal repetition"); }
即,如果出现{符号,并且后面的字符不是数字,那么就会抛出异常。
在JDK的文档中,对于正则表达式的关键字也有如下的说明:
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 at java.lang.String.charAt(String.java:444) at java.util.regex.Matcher.appendReplacement(Matcher.java:559) at java.util.regex.Matcher.replaceAll(Matcher.java:661) at java.lang.String.replaceAll(String.java:1663) at atis.StringReplace.main(StringReplace.java:13) Exception in thread "main"

这次通过使用Debug的方式(笔者偏向于这种方式,很多时候发现问题,自己找出并解决可以避免下次发生类似的错误,并且印象比较深刻,当然是在不花费太多时间为前提),可以发现问题出在:
java.util.regex.Matcher.appendReplacement(Matcher.java:559) while (cursor < replacement.length()) { char nextChar = replacement.charAt(cursor); if (nextChar == '//') { cursor++; nextChar = replacement.charAt(cursor); result.append(nextChar); cursor++; } else if (nextChar == '$') { // Skip past $ cursor++; // The first number is always a group int refNum = (int)replacement.charAt(cursor) - '0'; if ((refNum < 0)||(refNum > 9)) throw new IllegalArgumentException( "Illegal group reference"); cursor++;
这里字符/和$都是作为一种特殊情况来处理的,例子程序中替换的字符串为$,此时会使游标自增,而替换的字符串的长度为1,导致了ArrayIndexOutOfBoundsException。
而如果我们将替换字符修改为"$a",那么出现的异常变成了另外一种:
java.lang.IllegalArgumentException: Illegal group reference
问题发生的地方同样,当字符为$符号时,游标自增1,然后如果$的下一个字符的ASCII不是0~9之间,就会抛出上述异常。

究其原因,仍然是因为$也是正则表达式中的特殊符号,在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; import java.text.DecimalFormatSymbols; import java.text.MessageFormat; import java.util.Locale; public class StringReplace { public static void main(String[] args) { String price = "The price is {0}1000.00"; MessageFormat format = new MessageFormat(price); DecimalFormatSymbols dfs = new DecimalFormatSymbols(Locale.US); System.out.println(format.format(new String[] { dfs.getCurrencySymbol() })); } } 4.总结
本文主要介绍了在使用String.replaceAll时容易出现的两个问题,即String.replaceAll内部使用了正则表达式,对正则表达式的关键字需要进行格外小心的处理。
同时介绍了java.text.MessageFormat类如何进行消息中参数的替换。

你可能感兴趣的:(Tips)