Java字符串替换replaceAll,新手容易出现的转义错误

1 问题说明

最近的写java代码出现了下面的问题,这种替换明明在js中replaceAll根本不会有问题,java的replaceAll到底做了什么?
如果未来开发所有目标动态替换的功能,对待替换的字符串有什么要求?本文给出答案

//java中 oracel- >mysql
"SELECT NVL(AAA,0) AS CCC".replaceAll("NVL", "IFNULL");
//结果-->  SELECT IFNULL(AAA,0) AS CCC
"SELECT NVL(AAA,0) AS CCC".replaceAll("NVL(", "IFNULL(");
//结果-->  抛异常
"SELECT NVL(AAA,0) AS CCC".replaceAll("NVL\\(", "IFNULL(");
//结果-->  SELECT IFNULL(AAA,0) AS CCC  【加转义 好麻烦】
"SELECT NVL(AAA,0) AS CCC".replaceAll("NVL(AAA,0)", "IFNULL(AAA,0)");
//结果-->  SELECT NVL(AAA,0) AS CCC  【没有替换,为什么没有替换?】

可以看到 部分替换 是不符合我们最开的预期的

2 本文结论

不想看代码测试的可直接看我的结论

下面的Java 代码,在测试字符串替换的一些情况。这段代码使用了 replaceAll 方法来进行替换操作,但是在某些情况下出现了问题

针对测试用例,可以看到一些问题:

如果目标字符串中包含 . ? * ( ) [ ] { } ^ $ \ | + 等字符,就需要注意转义这些字符

对于包含正则表达式特殊字符的目标字符串,需要进行正确的转义才能得到预期的替换结果。
在某些情况下,在开发不知情或不熟悉的情况下,目标字符串中包含了这些特殊字符,下面的测试可以看出
会出现,极度不符合预期的结果 或者抛出异常(扣钱)

如果想要达到预期的替换效果,可以尝试以下方法

  1. 对目标字符串中的正则表达式特殊字符进行转义,使用Pattern.quote(strTar) 方法可以实现忽略正则表达式特殊字符的替换。[strTar是正常字符串就行,反而不能加转义符号]
  2. 如果不需要使用正则表达式进行替换,可以考虑 使用String.replace() 方法只能替换1次,这样可以避免正则表达式带来的复杂性和意外行为。
  3. 使使用js的replaceAll来进行替换,它可以自动实现调用 类似java的Pattern.quote的函数来避免歧义

更懒人的方法可以使用 文章末尾的工具类来避免这类问题

3 拓展说明

除了 replaceAll 方法外,Java 中还有一些其他字符串处理方法的参数 实际上是一个正则。这些方法包括:

1 String.matches(String regex):该方法用于判断字符串是否匹配给定的正则表达式模式。参数 regex 就是需要使用正则表达式的地方。

2 String.split(String regex):该方法根据给定的正则表达式模式将字符串拆分成字符串数组。参数 regex 指定了分隔符的匹配模式。【我们这个常用切割文件拓展名的 . 】

3 Pattern.compile(String regex):用于将给定的正则表达式编译成一个模式对象,后续可以使用该对象进行匹配操作。

4 Pattern.matches(String regex, CharSequence input):与 String.matches() 类似,用于判断输入的字符序列是否与给定的正则表达式模式匹配。

这些方法都接受一个正则表达式作为参数,因此在调用时需要注意正则表达式中的特殊字符,需要进行正确的转义或处理,避免意外的行为。

4 代码测试

private static void testStringFunction() {
	//测试环境jdk1.8
	final String[][] testArr = new String[][] {
		//原始字符串,目标字符串,要替换的字符串
		// [ 号的问题
		new String[]{"你好 [world]","[world]","世界"},	//【bug】 --> 你好 [世界世界世界世界世界]
		new String[]{"你好 [world]","[world","世界"},	//【bug】 --> 抛异常 字符串替换失败Unclosed character class near index 5 [world
		new String[]{"你好 [world]","\\[world","世界"},//【success】 --> 你好 世界] 【成功但写法复杂】
		// ( 号的问题 
		new String[]{"你好 (universe)","(universe)","宇宙"},	//【bug】 --> 你好 (宇宙) 【替换了,但不是用户想要的目标】
		new String[]{"你好 (universe)","(universe","宇宙"},	//【bug】 --> Unclosed group near index 9 (universe
		new String[]{"你好 (universe)","\\(universe","宇宙"}, // 【success】 --> 你好 宇宙)  【成功但写法复杂】
		// $ 号的问题 行尾
		new String[]{"你好 $100","$100","一个亿"}, //【bug】 --> 你好 $100 【没有执行替换】
		new String[]{"你好 $100","$","一个亿"}, //【bug】 --> 你好 $100一个亿 【替换了,但不是用户想要的目标】
		// ^ 号的问题 行首
		new String[]{"你好 ^100","^100","一百万"}, //【bug】 --> 你好 ^100 【没有执行替换】
		new String[]{"你好 ^100","^","一百万"}, //【bug】 --> 一百万你好 ^100 【替换了,但不是用户想要的目标】
		// . 号的问题
		new String[]{"你好 file.doc",".doc",".docx"}, //【success】 --> 你好 file.docx 【替换了,结果复合预期,但诡异】
		new String[]{"你好 file.doc",".","A"},        //【bug】 --> AAAAAAAAAAA 【替换了,但结果是错误的】
		// ? 号的问题  在正则表达式中通常用于表示零次或一次匹配
		new String[]{"你好 吃饭没?","没?","了"},        //【bug】 --> 了你了好了 了吃了饭了了?了 【替换了,但结果是错误的】
		new String[]{"你好 吃饭没?","?","有"},        //【bug】 --> 抛异常 Dangling meta character '?' near index 0 ?
		// * 号的问题  a* 表示匹配
		new String[]{"你好 字符串匹配真*垃圾","*","TM"}, //【bug】 --> 抛异常 Dangling meta character '*' near index 0 *
		new String[]{"你好 字符串匹配真*垃圾","真*","真TM"}, //【bug】 --> 真TM你真TM好真TM 真TM字真TM符真TM串真TM匹真TM配真TM真TM*真TM垃真TM圾真TM 【替换了,但结果是错误的】
		// + 号的问题  a+ 表示匹配
		new String[]{"你好 字符串匹配真+垃圾","+","TM"}, //【bug】 --> 抛异常 Dangling meta character '*' near index 0 +*
		new String[]{"你好 字符串匹配真+垃圾","真+","真TM"},//【bug】 --> 你好 字符串匹配真TM+垃圾 【替换了,但结果是错误的】
		// { 号的问题
		new String[]{"你好 {world}","{world}","世界"},	//【bug】 --> 抛异常 Illegal repetition
		new String[]{"你好 {world}","{world","世界"},	//【bug】 --> 抛异常 Illegal repetition
		new String[]{"你好 {world}","\\{world","世界"},//【success】 --> 你好 世界} 【成功但写法复杂】
	};
	int rowno = 0;
	final boolean useGoodReplaceAll = false;
	for (String[] hang : testArr) {
		rowno++;
		final String str = hang[0];
		final String strTar = hang[1];
		final String strDes = hang[2];
		System.out.println(rowno + " " + StringUtils.join(hang," ---- "));
		try {
			String strLast = "";
			if (useGoodReplaceAll) {
				strLast = str.replaceAll(Pattern.quote(strTar), strDes);
			} else {
				strLast = str.replaceAll(strTar, strDes);
			}
			System.out.println(rowno + " " + "结果:" + strLast);
		} catch (Exception e) {
			System.err.println(rowno + " " + "字符串替换失败" + e.getMessage());
		}
	}
	System.out.println("AA.BB.CC".split(".").length);//0
	System.out.println("AA.BB.CC".split("\\.").length);//3
	System.out.println("AA.BB.CC".split(Pattern.quote(".")).length);//3
}

4.1 结果图–有问题版

在这里插入图片描述

4.1 结果图–修复版

在这里插入图片描述

5 工具类

读完本文的赠品 ,工具类,喜欢的可以自己贴到代码中,这样就能像js一样 不考虑正则的影响做纯粹的替换
也不用特别的去加 \ 转义

e.g.
System.out.println("AA.doc".replaceAll(".",  "_"));
//输出为 ______

final String bb = CMUtil.replaceAll("AA.doc",  ".",  "_");
//bb 值为 AA_doc

final String cc = CMUtil.replaceAll("SELECT NVL(AAA,0) AS CCC",  "NVL(",  "IFNULL(");
//cc 值为 SELECT IFNULL(AAA,0) AS CCC
public class CMUtil {
	/**
	 * java字符串的替换函数,去正则化修正版本.
	 * @param str
	 * @param strTar
	 * @param strDes
	 * @return 
	 */
	public static String replaceAll(final String str, final String strTar, final String strDes){
		final String strTarGood = Pattern.quote(strTar);
		return new String(str).replaceAll(strTarGood, strDes);
	}
	/**
	 * java字符串的切割函数,去正则化修正版本.
	 * @param str
	 * @param strTar
	 * @param strDes
	 * @return
	 */
	public static String[] split(final String str, final String dot){
		final String strTarGood = Pattern.quote(dot);
		return new String(str).split(strTarGood);
	}
}
  • 6
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值