DecimalFormat模式字符串需区分“0”(必显补零)与“#”(可选不补零),禁用多小数点及“#”在“0”左;负数需用分号定义双模式;必须手动关闭分组、启用BigDecimal解析;非线程安全,须用ThreadLocal或每次新建实例。

DecimalFormat 的模式字符串怎么写才不会出错
模式字符串是 DecimalFormat 的核心,写错一个符号就可能让数字显示成 0、空字符串,甚至抛 IllegalArgumentException。关键不是记全所有符号,而是理解占位符的语义差异:
-
0表示「必须显示,不足补零」——比如模式"00.00"格式化1.5得到"01.50" -
#表示「可选,不补零」——"##.##"格式化1.5得到"1.5",格式化1.0得到"1" -
.是小数点分隔符(固定位置),,是千位分隔符(仅在整数部分起作用) - 模式中不能出现多个小数点,也不能把
#放在0左边(如"#0.00"合法,"0#.00"会抛异常)
常见翻车点:用 "#,##0.00" 格式化负数时,若没显式定义负数子模式,会自动加括号(如 -1234.5 → "(1,234.50)"),而不是带负号。要改这个行为,得用分号定义双模式:"#,##0.00;-,##0.00"。
setGroupingUsed(false) 和 setParseBigDecimal(true) 为什么必须手动设
DecimalFormat 默认开启分组(千位逗号),且解析结果是 Number(通常是 Double 或 Long),这对金融计算极其危险——double 无法精确表示 0.1,解析 "19.99" 可能变成 19.989999999999998。
- 关闭分组:
df.setGroupingUsed(false),避免格式化时插入逗号干扰后续解析或存储 - 启用 BigDecimal 解析:
df.setParseBigDecimal(true),确保parse("19.99")返回的是精确的BigDecimal,不是有误差的Double - 注意:这两个设置必须在构造完
DecimalFormat实例后立即调用,不能依赖默认值
DecimalFormat df = new DecimalFormat("#0.00");
df.setGroupingUsed(false);
df.setParseBigDecimal(true);
BigDecimal bd = (BigDecimal) df.parse("19.99"); // 安全
为什么 parse() 有时返回 Double 而不是 BigDecimal
即使调用了 setParseBigDecimal(true),parse(String) 方法仍可能返回 Double——前提是输入字符串不含小数点或指数(比如 "123")。这是 JDK 的实现细节:只有当解析逻辑需要保留精度时(即识别到小数部分),才会走 BigDecimal 分支。
立即学习“Java免费学习笔记(深入)”;
- 安全做法是始终强制转型并捕获异常:
BigDecimal bd = (BigDecimal) df.parse(s) - 更稳妥的方式是用
parse(String, ParsePosition),它不抛异常,允许你检查解析是否成功及位置是否耗尽 - 如果输入来源不可控(比如用户输入),建议先用正则粗筛格式(如
^-?\\d+(\\.\\d+)?$),再进DecimalFormat解析
线程安全问题:为什么不能全局复用同一个 DecimalFormat 实例
DecimalFormat 不是线程安全的。它的内部状态(如 parsePosition、缓存的 BigDecimal)会在并发调用 format() 或 parse() 时被污染。典型表现是:两个线程同时解析 "123.45" 和 "678.90",结果一个得到 "678.45",另一个得到 "123.90"。
- 不要将
DecimalFormat声明为static final - 推荐方案:用
ThreadLocal缓存实例,或每次新建(JDK 8+ 创建开销极小) - 如果用
ThreadLocal,记得重写initialValue()并设置好setGroupingUsed和setParseBigDecimal
最常被忽略的一点:模式字符串本身是只读的,但格式化/解析过程会修改内部状态。哪怕你只读不写,只要多个线程共用一个实例,就一定会出问题。










