1. 概述
本文将深入探讨 DecimalFormat
类及其实际应用场景。作为 NumberFormat
的子类,它允许我们通过预定义模式格式化数字的字符串表示,也能反向将字符串解析为数字。
2. 工作原理
格式化数字的核心是定义模式——由特殊字符和文本组成的序列。关键特殊字符包括:
字符 | 作用 |
---|---|
0 |
有数字则显示,无则补零 |
# |
有数字则显示,无则留空 |
. |
指定小数点位置 |
, |
指定分组分隔符位置 |
模式应用时,会根据 JVM 的 Locale
默认规则执行格式化(除非显式指定 Locale
)。以下示例均基于英文 Locale
环境。
3. 基础格式化
3.1 简单小数
double d = 1234567.89;
assertThat(
new DecimalFormat("#.##").format(d)).isEqualTo("1234567.89");
assertThat(
new DecimalFormat("0.00").format(d)).isEqualTo("1234567.89");
✅ 关键点:整数部分永远不会被丢弃,即使模式比数字短:
assertThat(new DecimalFormat("#########.###").format(d))
.isEqualTo("1234567.89");
assertThat(new DecimalFormat("000000000.000").format(d))
.isEqualTo("001234567.890");
当模式比数字长时:
0
会强制补零#
会自动舍弃
3.2 舍入处理
当模式的小数位不足时,会触发舍入:
assertThat(new DecimalFormat("#.#").format(d))
.isEqualTo("1234567.9"); // 0.89 → 0.9
assertThat(new DecimalFormat("#").format(d))
.isEqualTo("1234568"); // 0.89 → 1.0 → 整数+1
⚠️ 默认使用 HALF_EVEN
舍入模式,可通过 setRoundingMode()
自定义。
3.3 分组分隔
分组分隔符会自动重复应用:
assertThat(new DecimalFormat("#,###.#").format(d))
.isEqualTo("1,234,567.9");
assertThat(new DecimalFormat("#,###").format(d))
.isEqualTo("1,234,568");
3.4 多重分组模式
某些国家(如印度)使用变长分组模式(如 #,##,###.##
)。但 DecimalFormat
不支持这种复杂规则——它只保留最右侧的分组模式并全局应用。
❌ 踩坑:尝试使用 #,##,##,##,###
会被简化为 #,###,###,###
。解决方案:
- 自定义字符串处理逻辑
- 使用 ICU4J 的
DecimalFormat
3.5 混合文本
可在模式中嵌入文本:
assertThat(new DecimalFormat("The # number")
.format(d))
.isEqualTo("The 1234568 number");
特殊字符需转义:
assertThat(new DecimalFormat("The '#' # number")
.format(d))
.isEqualTo("The # 1234568 number");
4. 本地化格式
不同地区的数字符号差异显著(如意大利用 .
作分组符,,
作小数点)。强制指定 Locale
可避免 JVM 默认设置干扰:
assertThat(new DecimalFormat("#,###.##",
new DecimalFormatSymbols(Locale.ENGLISH)).format(d))
.isEqualTo("1,234,567.89");
assertThat(new DecimalFormat("#,###.##",
new DecimalFormatSymbols(Locale.ITALIAN)).format(d))
.isEqualTo("1.234.567,89");
自定义 Locale
示例:
Locale customLocale = new Locale("it", "IT");
assertThat(new DecimalFormat(
"#,###.##",
DecimalFormatSymbols.getInstance(customLocale)).format(d))
.isEqualTo("1.234.567,89");
5. 科学计数法
5.1 E 计数法
使用 E
表示十的指数:
assertThat(new DecimalFormat("00.#######E0").format(d))
.isEqualTo("12.3456789E5");
assertThat(new DecimalFormat("000.000000E0").format(d))
.isEqualTo("123.456789E4");
⚠️ 指数位数很重要:10^12
需用 E00
而非 E0
。
5.2 工程计数法
工程计数法要求指数是 3 的倍数(如 Kilo/Mega/Giga)。关键技巧:设置整数部分最大位数 > 最小位数且 > 1:
assertThat(new DecimalFormat("##0.######E0")
.format(d)).isEqualTo("1.23456789E6");
assertThat(new DecimalFormat("###.000000E0")
.format(d)).isEqualTo("1.23456789E6");
6. 字符串解析
通过 parse()
将字符串转为数字:
assertThat(new DecimalFormat("", new DecimalFormatSymbols(Locale.ENGLISH))
.parse("1234567.89"))
.isEqualTo(1234567.89);
assertThat(new DecimalFormat("", new DecimalFormatSymbols(Locale.ITALIAN))
.parse("1.234.567,89"))
.isEqualTo(1234567.89);
强制输出特定类型:
Number result = nf.parse("123.45");
double d = result.doubleValue(); // 转为 double
获取 BigDecimal
:
NumberFormat nf = new DecimalFormat(
"",
new DecimalFormatSymbols(Locale.ENGLISH));
((DecimalFormat) nf).setParseBigDecimal(true);
assertThat(nf.parse("1234567.89"))
.isEqualTo(BigDecimal.valueOf(1234567.89));
7. 线程安全
⚠️ 重要:DecimalFormat
非线程安全!多线程共享实例时需同步处理或使用 ThreadLocal
。
8. 总结
本文系统梳理了 DecimalFormat
的核心用法、优势与局限。掌握这些技巧,能让你在数字格式化场景中游刃有余。完整代码示例请参考 GitHub 仓库。