1. 概述

本文将深入探讨Java中强大的字符串格式化工具——java.util.Formatter类。这个类提供了灵活的布局对齐和格式化支持,让你能像C语言中的printf一样优雅地处理字符串格式化需求。

2. 如何使用Formatter

还记得C语言的printf吗?Java的字符串格式化与之非常相似。Formatter的核心功能通过String类的静态方法暴露出来。这个方法接收一个模板字符串和一组参数,用参数填充模板:

String greetings = String.format(
  "Hello Folks, welcome to %s !", 
  "Baeldung");

输出结果为:

"Hello Folks, welcome to Baeldung !"

模板字符串包含静态文本和格式说明符,后者指示参数在特定位置的插入方式。在这个例子中,%s就是格式说明符,它会被对应的参数替换。

3. 格式说明符详解

3.1. 通用语法

对于常规、字符和数值类型,格式说明符的语法结构如下:

%[argument_index$][flags][width][.precision]conversion

其中:

  • argument_index(可选):整数i,表示使用第i个参数
  • flags(可选):修改输出格式的字符集
  • width(可选):最小输出字符数
  • precision(可选):限制字符数的整数(具体行为取决于转换类型)
  • conversion(必需):指示参数如何格式化的字符

关键点conversion是唯一必需部分,其他都是可选修饰符。

如果想显式指定参数位置,可以使用1$2$等索引:

String greetings = String.format(
  "Hello %2$s, welcome to %1$s !", 
  "Baeldung", 
  "Folks");

3.2. 日期/时间表示

日期/时间格式说明符语法:

%[argument_index$][flags][width]conversion

⚠️ 注意:日期格式必须以tT开头,后跟特定的时间单位字符。

示例:

@Test
public void whenFormatSpecifierForCalendar_thenGotExpected() {
    Calendar c = new GregorianCalendar(2017, 11, 10);
    String s = String.format(
      "The date is: %tm %1$te,%1$tY", c);

    assertEquals("The date is: 12 10,2017", s);
}

这里:

  • %tm:两位数月份
  • %te:月份中的日期
  • %tY:四位数年份

3.3. 无参数格式说明符

语法:

%[flags][width]conversion

目前仅支持两种转换:

  • %%:输出百分号%
  • %n:输出平台特定的换行符

示例:

@Test
public void whenNoArguments_thenExpected() {
    String s = String.format("John scored 90%% in Fall semester");
 
    assertEquals("John scored 90% in Fall semester", s);
}

4. 转换类型详解

4.1. 通用转换

适用于任何参数类型:

  1. b/B:布尔值(null转为false
  2. h/H:哈希码
  3. s/S:字符串(null转为"null",否则调用toString()

示例:

@Test
public void givenString_whenGeneralConversion_thenConvertedString() {
    String s = String.format("The correct answer is %s", false);
    assertEquals("The correct answer is false", s);

    s = String.format("The correct answer is %b", null);
    assertEquals("The correct answer is false", s);

    s = String.format("The correct answer is %B", true);
    assertEquals("The correct answer is TRUE", s);
}

4.2. 字符转换

适用于Unicode字符类型:char, Character, byte, Byte, short, Short,以及有效的int/Integer

语法:c(小写)或C(大写)

示例:

@Test
public void givenString_whenCharConversion_thenConvertedString() {
    String s = String.format("The correct answer is %c", 'a');
    assertEquals("The correct answer is a", s);

    s = String.format("The correct answer is %c", null);
    assertEquals("The correct answer is null", s);

    s = String.format("The correct answer is %C", 'b');
    assertEquals("The correct answer is B", s);

    s = String.format("The valid unicode character: %c", 0x0400);
    assertTrue(Character.isValidCodePoint(0x0400));
    assertEquals("The valid unicode character: Ѐ", s);
}

踩坑:无效码点会抛出IllegalFormatCodePointException

@Test(expected = IllegalFormatCodePointException.class)
public void whenIllegalCodePointForConversion_thenError() {
    String s = String.format("The valid unicode character: %c", 0x11FFFF);
}

4.3. 数值 - 整数类型

适用于Java整数类型:byte, Byte, short, Short, int, Integer, long, Long, BigInteger

转换类型:

  1. d:十进制
  2. o:八进制
  3. x/X:十六进制

示例:

@Test
public void whenNumericIntegralConversion_thenConvertedString() {
    String s = String.format("The number 25 in decimal = %d", 25);
    assertEquals("The number 25 in decimal = 25", s);

    s = String.format("The number 25 in octal = %o", 25);
    assertEquals("The number 25 in octal = 31", s);

    s = String.format("The number 25 in hexadecimal = %x", 25);
    assertEquals("The number 25 in hexadecimal = 19", s);
}

4.4. 数值 - 浮点类型

适用于Java浮点类型:float, Float, double, Double, BigDecimal

转换类型:

  1. e/E:科学计数法
  2. f:十进制数
  3. g/G:根据精度自动选择科学计数法或十进制

示例:

@Test
public void whenNumericFloatingConversion_thenConvertedString() {
    String s = String.format(
      "The computerized scientific format of 10000.00 "
      + "= %e", 10000.00);
 
    assertEquals(
      "The computerized scientific format of 10000.00 = 1.000000e+04", s);
    
    String s2 = String.format("The decimal format of 10.019 = %f", 10.019);
    assertEquals("The decimal format of 10.019 = 10.019000", s2);
}

4.5. 其他转换

  1. 日期/时间:需使用t/T前缀(如%tY
  2. 百分号%%输出%
  3. 行分隔符%n输出平台换行符

示例:

@Test
public void whenLineSeparatorConversion_thenConvertedString() {
    String s = String.format("First Line %nSecond Line");
 
    assertEquals("First Line \n" + "Second Line", s);
}

5. 标志位

标志位用于修改输出格式。常用标志包括:

  • -:左对齐
  • 0:零填充
  • +:包含正负号
  • ,:包含千位分隔符

示例(左对齐标志):

@Test
public void whenSpecifyFlag_thenGotFormattedString() {
    String s = String.format("Without left justified flag: %5d", 25);
    assertEquals("Without left justified flag:    25", s);

    s = String.format("With left justified flag: %-5d", 25);
    assertEquals("With left justified flag: 25   ", s);
}

6. 精度控制

精度在不同转换类型中有不同含义:

  • 通用转换:最大输出字符数
  • 浮点转换:小数点后位数

示例:

@Test
public void whenSpecifyPrecision_thenGotExpected() {
    String s = String.format(
      "Output of 25.09878 with Precision 2: %.2f", 25.09878);
 
    assertEquals("Output of 25.09878 with Precision 2: 25.10", s);

    String s2 = String.format(
      "Output of general conversion type with Precision 2: %.2b", true);
 
    assertEquals("Output of general conversion type with Precision 2: tr", s2);
}

7. 参数索引

参数索引指定使用哪个参数:

  • 1$:第一个参数
  • 2$:第二个参数
  • <:复用前一个参数

示例:

@Test
public void whenSpecifyArgumentIndex_thenGotExpected() {
    Calendar c = Calendar.getInstance();
    String s = String.format("The date is: %tm %1$te,%1$tY", c);
    assertEquals("The date is: 12 10,2017", s);

    s = String.format("The date is: %tm %<te,%<tY", c);
    assertEquals("The date is: 12 10,2017", s);
}

技巧:使用<可以避免重复写索引,代码更简洁。

8. 其他使用方式

8.1. 与Appendable配合使用

直接创建Formatter实例并绑定到Appendable对象(如StringBuilder):

@Test
public void whenCreateFormatter_thenFormatterWithAppendable() {
    StringBuilder sb = new StringBuilder();
    Formatter formatter = new Formatter(sb);
    formatter.format("I am writting to a %s Instance.", sb.getClass());
    
    assertEquals(
      "I am writting to a class java.lang.StringBuilder Instance.", 
      sb.toString());
}

类似地,可以绑定到OutputStreamFile等目标。

9. 总结

java.util.Formatter提供了强大而灵活的字符串格式化能力。通过组合使用:

  • 格式说明符语法
  • 多种转换类型
  • 精度控制
  • 标志位修饰
  • 参数索引引用

我们可以实现从简单文本替换到复杂日期/时间格式化的各种需求。简单粗暴地说:掌握这个工具,Java字符串格式化从此无忧。

本文示例代码可在GitHub仓库获取。


原始标题:Guide to java.util.Formatter