字符串占位替换,相信没有小伙伴是陌生的,这东西可以说是伴随着我们所有的项目工程,编码过程;别不相信,如
- String.format
- sql 参数拼接的占位
- log 日志输出
接下来我们看一下在我们的日常工作生涯中,经常涉及到的几种占位替换方式
1. String.format
这种可以说是最原始最基础的方式了,基本上在最开始学习 java 这门语言的时候就会涉及到,语法也比较简单
举例如下
String.format("hello %s", "一灰灰blog”); 复制代码
使用%
来表示占位,后面跟上不同的标识符,用于限定这个占位处的参数类型
这种使用姿势,由 jdk 原生提供支持,下表为不同的转换符对应的说明
转换符 | 说明 | 参数实例 |
%s |
字符串替换 | "一灰灰" |
%c |
字符类型 | 'a' |
%b |
布尔类型 | true/false |
%d |
整数,十进制 | 10 |
%x |
整数,十六进制 | 0x12 |
%o |
整数,八进制 | 012 |
%f |
浮点 | 0.12f |
%e |
指数 | 2e2 |
%g |
通用浮点型 | |
%h |
散列 | |
%% |
百分比 | |
%n |
换行 | |
%tx |
日期与时间类型(x 代表不同的日期与时间转换符 |
虽然上面表中列出了很多,但实际使用时,%s
, %d
, %f
这三个就足以应付绝大部分的场景了;使用姿势和上面的实例参不多,第一个参数为字符串模板,后面的可变参数为待替换的值
下面是在实际使用过程中的注意事项
1.1 类型不匹配
上面的表中介绍了不同的转换符,要求的参数类型,如果没有对应上,会怎样
%s
,传入非字符串类型
@Test public void testFormat() { System.out.println(String.format("hello %s", 120)); System.out.println(String.format("hello %s", true)); System.out.println(String.format("hello %s", new int[]{1,2, 3})); System.out.println(String.format("hello %s", Arrays.asList(1, 2, 3))); System.out.println(String.format("hello %s", 0x12)); } 复制代码
输出如下
hello 120 hello true hello [I@3d82c5f3 hello [1, 2, 3] hello 18 复制代码
也就是说,%s
的占位标记,传参如果不是 String 类型,那么实际替换的是 arg.toString()
(所以数组输出的是地址,而 list 输出了内容)
%d
,传入非整数
与字符串的不一样的是,如果我们定义要求替换的参数类型为整数,那么传参不是整数,就会抛异常
System.out.println(String.format("hello %d", 1.0F)); System.out.println(String.format("hello %d", "10")); 复制代码
上面这两个,一个传入的参数为浮点,一个传入的是字符串,在实际替换的时候,可不会调用Integer.valufOf(String.valueOf(xxx))
来强转,而是采用更直接的方式,抛异常
关键的提示信息如下
java.util.IllegalFormatConversionException: d != java.lang.Float java.util.IllegalFormatConversionException: d != java.lang.String 复制代码
因此在实际使用这种方式进行替换时,推荐选择 %s
,毕竟兼容性更好
1.2 参数个数不匹配
我们会注意到,String.format
接收的参数是不定长的,那么就可能存在字符串模板中预留的占位与实际传入的参数个数不匹配的场景,那么出现这种场景时,会怎样
参数缺少
System.out.println(String.format("hello %s %s", "yihui")); 复制代码
上面的例子中,模板要求两个,实际只传入一个参数,会直接抛异常MissingFormatArgumentException
java.util.MissingFormatArgumentException: Format specifier '%s' 复制代码
参数过多
System.out.println(String.format("hello %s", "yihuihui", "blog")); 复制代码
执行正常,多余的参数不会被替换
因此,我们在使用String.format
进行字符串替换时,请确保传参不要少于实际定义的参数个数;多了还好,少了就会抛异常
2. MessageFormat
上面介绍的 String.format 虽说简单好用,但我们用多之后,自然会遇到,一个参数,需要替换模板中多个占位的场景,针对这种场景,更友好的方式是MessageFormat
,这个也是 jdk 原生提供的
我们来简单看一下它的使用姿势
String ans = MessageFormat.format("hello {0}, wechart site {0}{1}", "一灰灰", "blog"); 复制代码
使用{数字}
来表示占位,其中数字对应的是传参的下标,因此当一个参数需要复用时,使用 MessageFormat 就可以比较简单的实现了,上面就是一个实例,替换之后的字符串为
hello 一灰灰, wechart site 一灰灰blog 复制代码
接下来说一下它使用时的注意事项
2.1 {}成对出现
如果字符串中,只出现一个{
,而没有配套的}
,会抛异常
System.out.println(MessageFormat.format("hello }", 123)); System.out.println(MessageFormat.format("hello { world", 456)); 复制代码
注意上面两种 case,上面一个是有}
而缺少{
,这样是没有问题的;而下面那个则会抛异常
java.lang.IllegalArgumentException: Unmatched braces in the pattern. 复制代码
如果字符串中却是希望输出{
,可以使用单引号来处理
System.out.println(MessageFormat.format("hello '{' world", 456)); 复制代码
2.2 单引号
上面提到需要转移时,可以用单引号进行处理,在字符串模板的定义中,如果有单引号,需要各位注意
只有一个单引号,会导致后面所有占位都不生效
System.out.println(MessageFormat.format("hello {0}, I'm {1}", "一灰灰", "blog")); 复制代码
上面这个输出结果可能和我们实际希望的不一致
hello 一灰灰, Im {1} 复制代码
要解决上面这个,就是使用两个单引号
System.out.println(MessageFormat.format("hello {0}, I''m {1}", "一灰灰", "blog")); 复制代码
这样输出的就是我们预期的
hello 一灰灰, I'm blog 复制代码
2.3 序号省略
上面的定义中,已经明确要求我们在{}
中指定参数的序号,如果模板中没有指定会怎样?
System.out.println(messageFormat.format("hello {}, world", "yihuihui")); 复制代码
直接抛异常
java.lang.IllegalArgumentException: can't parse argument number: 复制代码
3. 小结
本文介绍的实战小技巧属于是 jdk 原生提供的两种实现字符串占位替换的方式,特别注意使用细节,如format的参数类型需要匹配,参数个数不能缺少;MessageFormat的单引号问题等
除了这两个之外,我们日常开发中还会遇到其他的占位替换方式
比如 sql 的?
替换,mybatis 中 sql 参数组装使用${paramName}
,或者 logback 日志输出中的{}
来表示占位,spring 的@Value 注解声明的配置注入方式
${name:defaultValue}
,这些也都属于占位替换的范畴,那么它们又是怎么实现的呢?
且看下文