利用 String format 方法及占位符优雅拼接字符串

介绍了 Java 语言里如何使用 String.format 去优雅地拼接字符串

不论是哪个编程语言, 拼接字符串都是一个频繁的操作, 很多的语言都支持通过重载"+"号运算符来拼接字符串, 这也是最常见快捷及直观的方式.

除了用"+"号拼接外, 有的语言还支持更先进的拼接方式, 比比较新版本的 javascript 语言还支持比较优雅的利用 ${XXX} 的方式去传递变量值, 这就已经基本接近了很多模板技术的写法了, 比如这样:

let name = '小明';
let age = 20;
console.log(`他的名字叫${name}, 他今年${age}岁.`); // 注意字符串用键盘左上角的撇号(`)括起来而不是引号

对于这样一个比较长又涉及好几个变量, 变量通常还是夹在字符串中间的情况, 如果使用传统的 + 号方式去拼接, 虽然不是不可以, 但往往显得支离破碎的, 写起来麻烦, 一堆的加号及引号, 看上去对整句的把握上也不友好直观:

let name = '小明';
let age = 20;
console.log('他的名字叫' + name + ', 他今年' + age + '岁.');

应该说, + 号式的拼接对于比较简短的, 只有一个变量接在最后的情况还是比较方便的, 但类似上述的情况, 往往就有点力不从心了, 而此时像 javascript 这种利用 ${XXX} 占位符的方式就友好很多了.

当然, 这是 javascript 的情况, Java 语言呢? 有点遗憾, Java 对于 ${XXX} 这种直接取变量名的方式仍然是不支持的, 不过它还是有自己的占位符方式, 还是可以避免把字符串弄得支离破碎的, 这个方式便是利用 String.format 方法结合 %x 占位符的方式, 比如上述例子可以这样:

package net.xiaogd.core.java.basic;

import org.junit.Assert;
import org.junit.Test;

public class StringFormatTest {
    @Test
    public void stringFormat() {
        String name = "小明";
        int age = 20;
        String str = String.format("他的名字叫%s, 他今年%d岁.", name, age);
        Assert.assertEquals("他的名字叫小明, 他今年20岁.", str);
    }
}

通过 %x 占位符的方式, 同样避免了使用 + 号时把字符串弄得支离破碎的情况, 直接在一个双引号里面就写完了.

在这里, 第一个变量 name, 也即是 String.format 的第二个参数, 会传递给字符串中的第一个占位符 %s:

%s 中 s 代表 string, 也即是字符串类型.

而第二个变量, 也即是 String.format 的第三个参数, 会传递给字符串中的第二个占位符 %d:

%d 中 d 代表 decimal integer, 也即是十进制整型.

如果字符串中有更多的占位符, 就相应传入更多的变量即可.

String.format 方法是一个可接收可变长参数的方法.

%X 的这种写法, 除了作为占位符之外, 还支持一定的格式化操作, 比如下述代码中, 用 %03d 的方式把不足三位的数字用前补零的方式补足三位去显示:

package net.xiaogd.core.java.basic;

import org.junit.Assert;
import org.junit.Test;

public class StringFormatTest {
    @Test
    public void stringFormatAlign() {
        // 把不足三位的数字用前补零的方式补足三位去显示
        String str = String.format("随机抽中的三个号码是 %03d, %03d, %03d.", 1, 135, 23);
        Assert.assertEquals("随机抽中的三个号码是 001, 135, 023.", str);
    }
}

在上述代码中, %d 前面说了, 是输出十进制整型用的, 而 %03d 中, 3 表示显示为 3 位的宽度, 0 则表示不足三位宽度的用前置零补足.

而如果不打算用零去填充, 而只是指定一个宽度, 则会用空格填充, 如下所示:

package net.xiaogd.core.java.basic;

import org.junit.Assert;
import org.junit.Test;

public class StringFormatTest {
    @Test
    public void stringFormatAlign2() {
        // 把不足四位的数字用前补空格的方式补足四位去显示
        // 为便于观察, 使用中括号[]括起内容
        String str2 = String.format("随机抽中的四个号码是 [%4d], [%4d], [%4d], [%4d].", 2, 1293, 43, 100);
        Assert.assertEquals("随机抽中的四个号码是 [   2], [1293], [  43], [ 100].", str2);
    }
}

另外, 上述的填充方式是右对齐的, 如果打算左对齐, 则可以使用"-"符号来实现:

package net.xiaogd.core.java.basic;

import org.junit.Assert;
import org.junit.Test;

public class StringFormatTest {
    @Test
    public void stringFormatAlign3() {
        // - 表示左对齐, 也即右补空格
        // 为便于观察, 使用中括号[]括起内容
        String str3 = String.format("随机抽中的三个号码是 [%-3d], [%-3d], [%-3d].", 1, 135, 23);
        Assert.assertEquals("随机抽中的三个号码是 [1  ], [135], [23 ].", str3);
    }
}

此外, 还支持变量的复用, 比如下述例子, 两个变量都分别被用了两次, 这时可以使用 %1$s, %2$s 这样的表示法, 其中, 1$, 2$ 表示依次取得第一个以及第二个参数的值, 如果有更多变量, 则依次类推:

package net.xiaogd.core.java.basic;

import org.junit.Assert;
import org.junit.Test;

public class StringFormatTest {

    @Test
    public void stringFormatReuse() {
        String xm = "小明";
        String xh = "小红";
        String str = String.format("%1$s喜欢%2$s, %2$s也喜欢%1$s.", xm, xh);
        Assert.assertEquals("小明喜欢小红, 小红也喜欢小明.", str);
    }
}

而如果不打算这样使用的话, 则需要传递四次变量, 每个依次对应一个 %s 的占位符, 如下述写法, 与前述做法结果一样:

package net.xiaogd.core.java.basic;

import org.junit.Assert;
import org.junit.Test;

public class StringFormatTest {
    @Test
    public void stringFormatNotReuse() {
        String xm = "小明";
        String xh = "小红";
        String str = String.format("%s喜欢%s, %s也喜欢%s.", xm, xh, xh, xm);
        Assert.assertEquals("小明喜欢小红, 小红也喜欢小明.", str);
    }
}

String.format 还支持更多的格式化的操作, 这个具体就要读者自己去看它的文档了.

其实它内部也是使用了 Formatter 这个类去实现的, 所以应该看的是 java.util.Formatter 这个类的文档, 里面有更多的用法介绍, 包括格式化小数, 日期, 货币等等的操作.

其实正如 format 这个方法名所暗示的, 它主要是关于格式化字符串的, 但它正好也给出了一种拼接字符串的较为优雅的方式, 所以, 即便你不关心字符串的格式化, 依然可以利用它的这个特性去做字符串常量与变量的拼接, 从而提高代码的可读性.