在Java开发中,经常需要把double或BigDecimal类型的数值转换成固定两位小数的字符串,例如金额展示、报表导出或对外接口传输。JDK自带的DecimalFormat就是专门处理这类需求的类,它通过模式字符串来定义输出格式,比手动拼接字符串或者使用String.format更灵活可控。

一、DecimalFormat基本用法
DecimalFormat位于java.text包下,最核心的概念是“模式串”。在模式串中,数字0代表强制显示位,如果原数位数不足就用0补齐;而#代表可选位,只有当对应位置有有效数字时才显示。小数点符号在模式中用点号表示,但它最终会根据Locale变成逗号或其他符号。
下面是一段最基础的代码示例,演示如何保留两位小数并补零:
import java.text.DecimalFormat;
public class Demo {
public static void main(String[] args) {
DecimalFormat df = new DecimalFormat("0.00");
// 输出 3.14
System.out.println(df.format(3.1));
// 输出 0.50
System.out.println(df.format(0.5));
// 输出 12.00
System.out.println(df.format(12));
}
}
从上面的代码可以看到,模式"0.00"保证了整数部分至少一位、小数部分严格两位。如果改用"#.##",那么3.1会输出成3.1而不是3.10,小数末尾的零会被丢弃。因此选择0还是#取决于业务是否要求强制补零。
除了直接new对象,也可以通过DecimalFormat.getInstance(Locale)获取本地化实例,但那样模式不可控,通常我们显式写模式串更稳妥。DecimalFormat是线程不安全的,在多线程环境里应当每次创建新实例,或者使用ThreadLocal封装。
二、RoundingMode与舍入规则
默认情况下,DecimalFormat使用的舍入模式是RoundingMode.HALF_EVEN,也就是银行家舍入:舍去位为5时,看前一位奇偶,偶数则舍、奇数则入。这在金融场景能降低累计偏差,但在普通展示中常让人困惑,例如2.005保留两位可能得到2.00而不是2.01。
我们可以通过setRoundingMode方法显式指定为HALF_UP,即常见的四舍五入:
import java.text.DecimalFormat;
import java.math.RoundingMode;
public class RoundDemo {
public static void main(String[] args) {
DecimalFormat df = new DecimalFormat("0.00");
df.setRoundingMode(RoundingMode.HALF_UP);
// 输出 2.01
System.out.println(df.format(2.005));
// 输出 1.99
System.out.println(df.format(1.985));
}
}
使用RoundingMode时,建议配合BigDecimal做中间计算,因为double本身存在二进制精度丢失,直接拿double进DecimalFormat可能得到意料外的结果。例如0.1加0.2得到的double并非0.3,格式化后也许是0.30,但更复杂的小数运算就可能翻车。
如果数据来自精确计算,推荐先用BigDecimal构造字符串再格式化,或者把RoundingMode和BigDecimal的scale设置统一,避免两层舍入逻辑打架。对于金额类系统,明确写明舍入策略比依赖默认值要安全得多。
三、Locale对小数点的影响
DecimalFormat会参考Locale决定小数点和分组分隔符。在Locale.GERMANY下,模式"0.00"格式化出来的不是3.14,而是3,14。如果这个结果直接写进JSON接口返回给前端,解析就会报错。
解决方式是指定Locale.US或者Locale.ROOT,强制使用点号作为小数分隔符:
import java.text.DecimalFormat;
import java.util.Locale;
public class LocaleDemo {
public static void main(String[] args) {
DecimalFormat df = new DecimalFormat("0.00");
df.setLocale(Locale.US);
// 明确输出 9.99
System.out.println(df.format(9.991));
}
}
在Web服务或微服务架构中,服务器默认Locale常常不确定,所以任何涉及数字格式化的地方都最好显式传Locale,不要依赖运行环境。特别是容器化部署后,基础镜像可能是精简版Linux,默认Locale和美国、中国都不太一样。
另外,如果既要千分位分隔又要两位小数,可以使用模式"#,##0.00",它会把整数部分三位一逗号,小数部分两位。但要注意分隔符同样受Locale控制,对外输出前务必确认符号符合接口约定。
四、常见误区与最佳实践
一个典型误区是用DecimalFormat做四舍五入然后计算,结果又拿格式化后的字符串转回数字参与运算。字符串格式化只是展示层工具,转回数字既慢又容易因Locale出错,应当始终用BigDecimal做运算、用DecimalFormat只做最后一步展示。
另一个坑是复用同一个DecimalFormat实例并调用applyPattern修改模式,在并发下会造成模式串混乱。简单做法是写成工具方法,每次new DecimalFormat,或者用局部变量:
import java.text.DecimalFormat;
import java.math.RoundingMode;
import java.util.Locale;
public class FormatUtil {
public static String twoDecimal(double value) {
DecimalFormat df = new DecimalFormat("0.00");
df.setRoundingMode(RoundingMode.HALF_UP);
df.setLocale(Locale.US);
return df.format(value);
}
}
这样调用方无需关心模式和Locale,统一输出两位小数的点号字符串。如果系统对性能极其敏感,可以用ThreadLocal<DecimalFormat>缓存实例,但必须保证每个线程拿到的实例不会被别的线程并发修改。
总结来说,DecimalFormat是Java里处理数字展示的强大工具,理解0与#的区别、主动设置RoundingMode和Locale,就能稳定地把任意数字格式化为保留两位小数,而不掉进隐式舍入或符号错乱的陷阱。
JavaDecimalFormat数字格式化修改时间:2026-08-01 15:03:30