String.replaceAll方法详析(正则妙用)
前言
我通常是不太关心代码的具体实现的,因为我的开发语言很杂,倾向于一些最简单通用的方式去解决。今儿不小心在群里看到一位朋友发了下面的java代码,感觉自己还是很局限很无知的:
Stringstr1="createTime"; Stringstr2="createTimeAt"; Stringregex="([A-Z])+"; System.out.println(str1.replaceAll(regex,"_$1").toLowerCase()); System.out.println(str2.replaceAll(regex,"_$1").toLowerCase()); //result //create_time //create_time_at
通过输出可以看到,这段代码的作用是把驼峰命名格式的字符串替换成下划线分割,这个功能比较简单,但是吸引我的却是他的代码。
"createTime".replaceAll("([A-Z]+)","_$1")
这行代码简单的很,就是调用了String类的replaceAll方法,方法的第一个参数是正则表达式,第二个参数是将要被替换成的新值。
让我惊奇的是他代码中,replaceAll的第二个参数,也就是JDK文档中名为replacement的参数,竟然是_$1。这是什么鬼?还支持类似占位符这样的东西?我一直都不知道。
问题探索
由于之前研究过一段正则表达式,通过观察replaceAll的第一个参数([A-Z]+),我猜想,这个应该是用到了正则表达式的分组,对应JDK中,就是java.util.regex.Matcher类的group()方法。
在Linux的Sed命令上,就使用&进行了一些替换,道理应该是相通的。
于是看了下String.replaceAll方法是如何实现的。JDK:
publicStringreplaceAll(Stringregex,Stringreplacement){ returnPattern.compile(regex).matcher(this).replaceAll(replacement); }
哦,原来它底层就是用了Matcher,只不过用的是Matcher自己的replaceAll方法。
去看它的文档,这个方法的参数果然有鬼,看下面实现代码。
publicStringreplaceAll(Stringreplacement){ reset(); booleanresult=find(); if(result){ StringBuildersb=newStringBuilder(); do{ appendReplacement(sb,replacement); result=find(); }while(result); appendTail(sb); returnsb.toString(); } returntext.toString(); }
里面关键的部分就是文档中说的appendReplacement方法,然后可以看到详细的描述文档。
看到这里明白了,原来这个方法的replacement参数可以通过$字符来指代Matcher通过正则匹配得到的分组,支持name和number两种方式,这里对应的就是Matcher类的group(name)和group(int)两个方法。
结论
1、String的replaceAll方法实际上是通过java.util.regex.Matcher类的replaceAll()方法实现的。
2、java.util.regex.Matcher类的replaceAll方法又是通过调用appendReplacement方法实现替换逻辑
3、Matcher类的appendReplacement方法的replacement参数支持通过$符号来指代Matcher匹配的分组
下面这串代码,就是使用Matcher类分组的一个最佳实践。
Stringdata="哈哈哈,xjjdog的手机号码是:12345678901,你会打给我吗"; //通过Matcher的分组功能,可以提取出上面字符串中的手机号 Matchermatcher=Pattern.compile(".*(xjjdog的手机号码是:([0-9]{11}))").matcher(data); while(matcher.find()){ System.out.println("G0:"+matcher.group(0)); System.out.println("G1:"+matcher.group(1)); System.out.println("G2:"+matcher.group(2)); } //result //G0:哈哈哈,xjjdog的手机号码是:12345678901 //G1:xjjdog的手机号码是:12345678901 //G2:12345678901
group(0)表示整个字符串
group(1)表示第一个匹配的,上面的例子中就是(我的手机号码是:([0-9]{11}))部分
group(2)表示第二个匹配的,上面的例子中就是([0-9]{11})部分
使用分组可以用来提取字符串中的目标字符串值,很好用!
几个例子
下面是几个例子,大家可以触类旁通。
驼峰转下划线命名
publicstaticStringcamelToUnderline(StringcamelName){ returncamelName.replaceAll("([A-Z]+)","_$1").toLowerCase(); }
下划线转驼峰
这个稍微麻烦点,是模仿者Matcher.replaceAll方法写的。
publicstaticStringunderlineToCamel(StringunderlineName){ Matchermatcher=Pattern.compile("(_[a-z]{1})").matcher(underlineName); StringBufferresult=newStringBuffer(); while(matcher.find()){ Stringreplacement=matcher.group(1); matcher.appendReplacement(result,replacement.replace("_","").toUpperCase()); } matcher.appendTail(result); returnresult.toString(); }
另外,MybatisGenerator插件源码中的也提供了类似方法(JavaBeansUtil.getCamelCaseString),这里做了下简单修改
publicstaticStringgetCamelCaseString(StringinputString){ StringBuildersb=newStringBuilder(); booleannextUpperCase=false; for(inti=0;i0){ nextUpperCase=true; } break; default: if(nextUpperCase){ sb.append(Character.toUpperCase(c)); nextUpperCase=false; }else{ sb.append(Character.toLowerCase(c)); } break; } } returnsb.toString(); }
没有复杂的正则参与,速度显而快了不少。
总结
看一些优秀的开源代码,确实能够了解到一些实用的技巧。这比起自己费劲心力重复制造一些轮子,要高效的多。时间要用在刀刃上,但不是用来切豆腐。