java小知识之String.format中的%秘密

String.format是Java中String类非常常用的一个方法,可以帮我们将占位符替换成变量,比如%d可以作为整型的占位符,%s可以作为字符串的占位符,但是吧有的时候常用归常用,有的时候碰到问题了还是得学习记录下
比如我们要写一个字符串替换方法

1
2
3
4
5
public class Demo {
public static void main(String[] args) {
System.out.printf("成功是%d%的努力和%d%的天赋", 99, 1);
}
}

就是希望输出 “成功是99%的努力和1%的天赋” ,先不管这句话的正确性,看看能不能正常输出(肯定是不能)

1
2
3
4
5
6
7
Exception in thread "main" java.util.UnknownFormatConversionException: Conversion = '的'
at java.util.Formatter.checkText(Formatter.java:2579)
at java.util.Formatter.parse(Formatter.java:2555)
at java.util.Formatter.format(Formatter.java:2501)
at java.io.PrintStream.format(PrintStream.java:970)
at java.io.PrintStream.printf(PrintStream.java:871)
at demo.Demo.main(Demo.java:9)

会有这样的报错,虽然是个小问题我们还是来仔细研究下,
首先这边format函数会根据可能的占位符去提取并分割字符串

1
2
3
4
5
6
7
8
9
10
11
private static final String formatSpecifier
= "%(\\d+\\$)?([-#+ 0,(\\<]*)?(\\d+)?(\\.\\d+)?([tT])?([a-zA-Z%])";

private static Pattern fsPattern = Pattern.compile(formatSpecifier);

/**
* Finds format specifiers in the format string.
*/
private FormatString[] parse(String s) {
ArrayList<FormatString> al = new ArrayList<>();
Matcher m = fsPattern.matcher(s);

比如上面的字符串,可以提取到两个有效的 %d
然后对剩余的进行分割,分割后再遍历校验每一个字符
如果碰到单独的 % 就说明出现了错误的分隔符
会调用 checkText 来识别报错
而如果我们想要能够正常输出% 也非常简单,只需要使用两个百分号 %%
这里其实就是匹配到了上面 formatSpecifier 的最后面,
%后面可以匹配的可以使 a-zA-Z 以及 %
当是连续的两个 %% 时就会被识别成转义成实际的 %

1
2
3
public static void main(String[] args) {
System.out.printf("成功是%d%%的努力和%d%%的天赋", 99, 1);
}

换成这样就能正常输出了