彻底搞懂 String.format() 的用法

String 的 format() 方法用来将指定对象格式化为指定格式的字符串。

String 的 format() 方法用来将指定对象格式化为指定格式的字符串,该方法定义如下:

static String format(Locale l, String format, Object... args)

使用指定的区域设置(locale)、格式字符串(format)和参数(args)返回格式化的字符串。args 表示格式字符串(format参数)中格式说明符引用的参数。如果比格式说明符更多的参数,额外的参数将被忽略。注意,参数的数量是可变的,可能为零。

static String format(String format, Object... args)

使用指定的格式字符串和参数返回格式化的字符串。注意,默认总是使用 Locale.getDefault() 方法返回的区域设置。

简单示例

格式化一个浮点数,要求浮点数保留两位小数,如下:

import java.util.Locale;

public class Demo {

    public static void main(String[] args) {
        // 保留两位小数
        String str = String.format("%.2f", 128.5674);
        System.out.println("str=" + str);

        str = String.format(Locale.getDefault(), "%.2f", 128.5674);
        System.out.println("str=" + str);
    }

}

运行结果:

str=128.57
str=128.57

从输出可知,format() 对小数点位数截取时采用了四舍五入。

简单原理

我们可以通过 IDEA 查看一下 format() 的源码,看看是怎样实现的,对我们理解更有帮助。源码如下:

public static String format(String format, Object... args) {
    return new Formatter().format(format, args).toString();
}

public static String format(Locale l, String format, Object... args) {
    return new Formatter(l).format(format, args).toString();
}

从源码可知,format() 内部均是通过创建 Formatter 的实例,调用它的 format() 方法来实现,接下来我们将仔细看看 Formatter 类的用法。

Formatter 类

Formatter 类是 printf 风格格式字符串的解释器。此类提供了对布局对齐和排列的支持,以及对数值、字符串和日期/时间数据的常规格式和特定于语言环境的输出的支持。支持诸如 byte、BigDecimal 和 Calendar 等常见 Java 类型。任意用户类型的受限格式化定制都是通过 Formattable 接口提供的。

Java 语言的格式化输出在很大程度上受到 C 语言 printf 的启发。虽然一些格式字符串与 C 类似,但已进行了某些定制,以适应 Java 语言,并且利用了其中一些特性。此外,Java 的格式比 C 的格式更严格;例如,如果转换与标志不兼容,则会抛出异常。在 C 中,不适用的标志会被忽略。

注意:对于多线程访问,Formatter 不是线程安全的。

用法示例:

StringBuilder sb = new StringBuilder();
// 将所有的输出发送到 Appendable 对象 sb 中
Formatter formatter = new Formatter(sb, Locale.US);

// 明确的参数索引可用于重新排列输出顺序
formatter.format("%4$2s %3$2s %2$2s %1$2s", "a", "b", "c", "d")
// -> " d  c  b  a"

// 可选的 locale 作为第一个参数,可用于获得针对本地化的数字格式。
// 可以给出精度和宽度,以便对数值进行舍入和对齐。
formatter.format(Locale.FRANCE, "e = %+10.4f", Math.E);
// -> "e =    +2,7183"
// 其中,10 表示宽度,4 表示精度

// '(' 数字标志可用于用括号而不是减号格式化负数。会自动插入组分隔符。
formatter.format("Amount gained or lost since last statement: $ %(,.2f",
                balanceDelta);
// -> "Amount gained or lost since last statement: $ (6,217.58)"

便捷用法示例:

// 写一个格式化的字符串到 System.out
System.out.format("Local time: %tT", Calendar.getInstance());
// -> "Local time: 13:34:18"

// 将格式化的输出写入 System.err
System.err.printf("Unable to open file '%1$s': %2$s",
                 fileName, exception.getMessage());
// -> "Unable to open file 'food': No such file or directory"

与 C 语言的 sprintf() 类似,可以使用静态方法 String.format() 来格式化字符串:

// 格式化一个包含日期的字符串
import java.util.Calendar;
import java.util.GregorianCalendar;
import static java.util.Calendar.*;

Calendar c = new GregorianCalendar(1995, MAY, 23);
String s = String.format("Duke's Birthday: %1$tm %1$te,%1$tY", c);
// -> s == "Duke's Birthday: May 23, 1995"

格式字符串语法

产生格式化输出的每个方法都需要格式字符串和参数列表。格式字符串是一个 String,它可以包含固定文本以及一个或多个嵌入的格式说明符。如下:

Calendar c = ...;
String s = String.format("Duke's Birthday: %1$tm %1$te,%1$tY", c);

此格式字符串是 format 方法的第一个参数。它包含三个格式说明符 "%1$tm"、"%1$te" 和 "%1$tY",它们指出应该如何处理参数以及在文本的什么地方插入它们。格式字符串的其余部分是包括 "Dukes Birthday: " 和其他任何空格或标点符号的固定文本。参数列表由传递给位于格式字符串之后的方法的所有参数组成。在上述示例中,参数列表的大小为 1,由对象 Calendar c 组成。

常规类型、字符类型和数值类型

语法如下:

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

其中:

  • 可选的 argument_index 是一个十进制整数,用于表明参数在参数列表中的位置。第一个参数由 "1$" 引用,第二个参数由 "2$" 引用,依此类推。

  • 可选的 flags 是修改输出格式的字符集,有效标志集取决于转换类型。

  • 可选的 width 是一个非负十进制整数,表明要向输出中写入的最少字符数。

  • 可选的 precision 是一个非负十进制整数,通常用来限制字符数。特定行为取决于转换类型。

  • conversion 是一个表明应该如何格式化参数的字符,给定参数的有效转换集取决于参数的数据类型。

日期和时间类型

语法如下:

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

其中: 

  • 可选的 argument_index、flags 和 width 的定义同上。

  • conversion 是一个由两字符组成的序列。第一个字符是 't' 或 'T'。第二个字符表明所使用的格式。这些字符类似于但不完全等同于那些由 GNU date 和 POSIX strftime(3c) 定义的字符。

其他类型

语法如下:

%[flags][width]conversion

其中: 

  • 可选 flags 和 width 的定义同上。

  • conversion 是一个表明要在输出中所插内容的字符。

转换(conversion)

下面列举了可用于 conversion 参数的转换值,转换可分为以下几类:

常规类型

可应用于任何参数类型,取值如下:

  • 'b', 'B':如果参数 arg 为 null,则结果为 "false"。如果 arg 是一个 boolean 值或 Boolean,则结果为 String.valueOf() 返回的字符串。否则结果为 "true"。

  • 'h', 'H':如果参数 arg 为 null,则结果为 "null"。否则,结果为调用 Integer.toHexString(arg.hashCode()) 得到的结果。

  • 's', 'S':如果参数 arg 为 null,则结果为 "null"。如果 arg 实现 Formattable,则调用 arg.formatTo()。否则,结果为调用 arg.toString() 得到的结果。

字符类型

可应用于表示 Unicode 字符的基本类型:char、Character、byte、Byte、short 和 Short。当 Character.isValidCodePoint(int) 返回 true 时,可将此转换应用于 int 和 Integer 类型。取值如下:

  • 'c', 'C':结果是一个 Unicode 字符

整数类型

可应用于 Java 的整数类型 byte、Byte、short、Short、int、Integer、long、Long 和 BigInteger。取值如下:

  • 'd':结果被格式化为十进制整数

  • 'o':结果被格式化为八进制整数

  • 'x', 'X':结果被格式化为十六进制整数

浮点数类型

可用于 Java 的浮点类型 float、Float、double、Double 和 BigDecimal。取值如下:

  • 'e', 'E':结果被格式化为用计算机科学记数法表示的十进制数

  • 'f':结果被格式化为十进制数

  • 'g', 'G':根据精度和舍入运算后的值,使用计算机科学记数形式或十进制格式对结果进行格式化。

  • 'a', 'A':结果被格式化为带有效位数和指数的十六进制浮点数

日期/时间类型

可应用于 Java 能够对日期或时间进行编码的类型 long、Long、Calendar 和 Date。取值如下:

  • 't', 'T':日期和时间转换字符的前缀

以下转换字符用来格式化时间:

  • 'H' 24 小时制的小时,被格式化为必要时带前导零的两位数,即 00 - 23。

  • 'I' 12 小时制的小时,被格式化为必要时带前导零的两位数,即 01 - 12。

  • 'k' 24 小时制的小时,即 0 - 23。

  • 'l' 12 小时制的小时,即 1 - 12。

  • 'M' 小时中的分钟,被格式化为必要时带前导零的两位数,即 00 - 59。

  • 'S' 分钟中的秒,被格式化为必要时带前导零的两位数,即 00 - 60 ("60" 是支持闰秒所需的一个特殊值)。

  • 'L' 秒中的毫秒,被格式化为必要时带前导零的三位数,即 000 - 999。

  • 'N' 秒中的毫微秒,被格式化为必要时带前导零的九位数,即 000000000 - 999999999。

  • 'p' 特定于语言环境的 上午或下午 标记以小写形式表示,例如 "am" 或 "pm"。使用转换前缀 'T' 可以强行将此输出转换为大写形式。

  • 'z' 相对于 GMT 的 RFC 822 格式的数字时区偏移量,例如 -0800。

  • 'Z' 表示时区缩写形式的字符串。Formatter 的语言环境将取代参数的语言环境(如果有)。

  • 's' 自协调世界时 (UTC) 1970 年 1 月 1 日 00:00:00 至现在所经过的秒数,即 Long.MIN_VALUE/1000 与 Long.MAX_VALUE/1000 之间的差值。

  • 'Q' 自协调世界时 (UTC) 1970 年 1 月 1 日 00:00:00 至现在所经过的毫秒数,即 Long.MIN_VALUE 与 Long.MAX_VALUE 之间的差值。

以下转换字符用来格式化日期:

  • 'B' 特定于语言环境的月份全称,例如 "January" 和 "February"。

  • 'b' 特定于语言环境的月份简称,例如 "Jan" 和 "Feb"。

  • 'h' 与 'b' 相同。

  • 'A' 特定于语言环境的星期几全称,例如 "Sunday" 和 "Monday"

  • 'a' 特定于语言环境的星期几简称,例如 "Sun" 和 "Mon"

  • 'C' 除以 100 的四位数表示的年份,被格式化为必要时带前导零的两位数,即 00 - 99

  • 'Y' 年份,被格式化为必要时带前导零的四位数(至少),例如,0092 等于格里高利历的 92 CE。

  • 'y' 年份的最后两位数,被格式化为必要时带前导零的两位数,即 00 - 99。

  • 'j' 一年中的天数,被格式化为必要时带前导零的三位数,例如,对于格里高利历是 001 - 366。

  • 'm' 月份,被格式化为必要时带前导零的两位数,即 01 - 13。

  • 'd' 一个月中的天数,被格式化为必要时带前导零两位数,即 01 - 31

  • 'e' 一个月中的天数,被格式化为两位数,即 1 - 31。

以下转换字符用于格式化常见的日期/时间组合:

  • 'R' 24 小时制的时间,被格式化为 "%tH:%tM"

  • 'T' 24 小时制的时间,被格式化为 "%tH:%tM:%tS"。

  • 'r' 12 小时制的时间,被格式化为 "%tI:%tM:%tS %Tp"。上午或下午标记 ('%Tp') 的位置可能与语言环境有关。

  • 'D' 日期,被格式化为 "%tm/%td/%ty"。

  • 'F' ISO 8601 格式的完整日期,被格式化为 "%tY-%tm-%td"。

  • 'c' 日期和时间,被格式化为 "%ta %tb %td %tT %tZ %tY",例如 "Sun Jul 20 16:17:00 EDT 1969"。

百分比

产生字面值 '%' ('u0025'),取值如下:

  • '%':结果为字面值 '%' ('u0025')

行分隔符

产生特定于平台的行分隔符,取值如下:

  • 'n':结果为特定于平台的行分隔符

标志(flags)

下表总结了受支持的标志(flags)。如下:

常规类型
  • '-':结果将是左对齐的

  • '#' :结果应该使用依赖于转换类型的替换形式,取决于 Formattable 的定义

字符类型
  • '-':结果将是左对齐的

整数类型
  • '-':结果将是左对齐的

  • '#' :结果应该使用依赖于转换类型的替换形式,只适用于 'o'、'x' 和 'X' 转换。

  • '+':结果总是包括一个符号,对 BigInteger 应用 'd'、'o'、'x' 和 'X' 转换时,或者对 byte 及 Byte、short 及 Short、int 及 Integer、long 及 Long 分别应用 'd' 转换时适用。

  • '  ':对于正值,结果中将包括一个前导空格。对 BigInteger 应用 'd'、'o'、'x' 和 'X' 转换时,或者对 byte 及 Byte、short 及 Short、int 及 Integer、long 及 Long 分别应用 'd' 转换时适用。

  • '0':结果将用零来填充

  • ',':结果将包括特定于语言环境的组分隔符。只适用于 'd' 转换。

  • '(':结果将是用圆括号括起来的负数。对 BigInteger 应用 'd'、'o'、'x' 和 'X' 转换时,或者对 byte 及 Byte、short 及 Short、int 及 Integer、long 及 Long 分别应用 'd' 转换时适用。

浮点数类型
  • '-':结果将是左对齐的

  • '#' :结果应该使用依赖于转换类型的替换形式

  • '  ':对于正值,结果中将包括一个前导空格

  • '0':结果将用零来填充

  • ',':结果将包括特定于语言环境的组分隔符。只适用于 'e'、'E'、'f'、'g' 和 'G' 转换。

  • '(':结果将是用圆括号括起来的负数。只适用于 'e'、'E'、'f'、'g' 和 'G' 转换。

日期/时间类型
  • '-':结果将是左对齐的

宽度(width)

宽度是将向输出中写入的最少字符数。对于行分隔符转换,不适用宽度,如果提供宽度,则会抛出异常。

精度(precision)

对于常规参数类型,精度是将向输出中写入的最多字符数。

对于浮点转换 'e'、'E' 和 'f',精度是小数点分隔符后的位数。如果转换是 'g' 或 'G',那么精度是舍入计算后所得数值的所有位数。如果转换是 'a' 或 'A',则不必指定精度。

对于字符、整数和日期/时间参数类型转换,以及百分比和行分隔符转换,精度是不适用的;如果提供精度,则会抛出异常。

参数索引(argument_index$)

参数索引是一个十进制整数,用于表明参数在参数列表中的位置。第一个参数由 "1$" 引用,第二个参数由 "2$" 引用,依此类推。

根据位置引用参数的另一种方法是使用 '<' ('u003c') 标志,这将会重用以前格式说明符的参数。例如:以下两条语句产生的字符相同:

Calendar c = ...;
String s1 = String.format("Duke's Birthday: %1$tm %1$te,%1$tY", c);
String s2 = String.format("Duke's Birthday: %1$tm %<te,%<tY", c);

示例

// 常规类型
System.out.println(String.format("%b", true)); // true
System.out.println(String.format("%h", "1024")); // 1700a1 散列码,即 hashcode
System.out.println(String.format("%s", "hello")); // hello

// 字符类型
System.out.println(String.format("%c", 65)); // A
System.out.println(String.format("%s", "hello world")); // hello world
System.out.println(String.format("%2$s", "hello", "world")); // world

// 数字类型
System.out.println(String.format("%10.4f", Math.PI)); //     3.1416
System.out.println(String.format("%-10.4f", Math.PI) + "|"); // 3.1416    |
System.out.println(String.format("%#x", 1024)); // 0x400 十六进制
System.out.println(String.format("%#o", 1024)); // 02000 八进制
System.out.println(String.format("%e", Float.MAX_VALUE)); // 3.402823e+38
System.out.println(String.format("%g", Math.PI)); // 3.14159
System.out.println(String.format("%a", Math.PI)); // 0x1.921fb54442d18p1
System.out.println(String.format("%(d", -1024)); // (1024)  负数表示
System.out.println(String.format("%(d", 1024)); // 1024
System.out.println(String.format("%010d", 1024)); // 0000001024 使用0填充
System.out.println(String.format("% 10d", 1024)); //       1024 使用空格填充
System.out.println(String.format("%+d %d %+d %d", 10, 10, -10, -10)); // +10 10 -10 -10

// 日期/时间类型
System.out.println(String.format("%tF", new Date())); // 2023-03-29
System.out.println(String.format("%tT", new Date())); // 12:47:22
System.out.println(String.format("%1$tY-%1$tm-%1$td", new Date())); // 2023-03-29
System.out.println(String.format("%1$tH:%1$tM:%1$tS", new Date())); // 12:47:22
System.out.println(String.format("%1$tH:%<tM:%<tS", new Date())); // 12:47:22

总结

%s 字符串类型           %c 字符类型

%d 十进制整数           %x 十六进制整数

%o 八进制整数           %b 布尔值类型

%f 浮点数               %a 十六进制浮点数

%g 通用浮点数           %e 指数形式

%h 散列码               %% 百分号

%n 换行

锲而舍之,朽木不折;锲而不舍,金石可镂。——《荀子·劝学》
0 不喜欢
说说我的看法 -
全部评论(
没有评论
关于
本网站专注于 Java、数据库(MySQL、Oracle)、Linux、软件架构及大数据等多领域技术知识分享。涵盖丰富的原创与精选技术文章,助力技术传播与交流。无论是技术新手渴望入门,还是资深开发者寻求进阶,这里都能为您提供深度见解与实用经验,让复杂编码变得轻松易懂,携手共赴技术提升新高度。如有侵权,请来信告知:hxstrive@outlook.com
公众号