背景信息
最近在一次使用历史功能导出表格的时候,出现了一个格式转换的报错,具体的报错信息如下
Caused by: java.lang.IllegalArgumentException: Cannot format given Object as a Number at java.text.DecimalFormat.format(DecimalFormat.java:507) at java.text.Format.format(Format.java:157) at com.iss.cms.drpf.common.service.impl.ExportServiceImpl.formatterData(ExportServiceImpl.java:281)
这个报错信息的具体原因就是在使用 DecimalFormat.format() 对数据进行格式转换时,由于传入的数据格式不匹配而导致的报错,下面来具体说说 DecimalFormat.format() 是什么,怎么用代码健壮性更好。
DecimalFormat.format()
DecimalFormat.format() 是 Java 中 java.text.DecimalFormat 类的核心方法,它用于将数字(如 double、long 或 BigDecimal 等)按照指定的格式模式转换为格式化的字符串。该方法允许开发者自定义数字的显示方式,包括控制小数位数、添加千位分隔符、设置百分比格式、科学计数法等,并支持本地化显示和舍入规则设置,是实现数字显示格式化的强大工具。
核心用法
对于 DecimalFormat.format() 格式化数字的类,我们通常会用在对金额的格式化上,比如将金额数据改成 120,111.00 这样的数据格式,常用的用法如下
基本格式
这里我们用一段代码来展示一下最常用的方式,比如
package org.example; import java.text.DecimalFormat; public class Main2 { public static void main(String[] args) { double number = 12345.6789; // 1. 创建 DecimalFormat 对象 DecimalFormat df = new DecimalFormat("#,##0.00"); // 2. 格式化数字 String formatted = df.format(number); // 输出: 12,345.68 System.out.println(formatted); } }
执行后的输出结果如图
那么 DecimalFormat.format() 除了 #,##0.00 格式化符号,还有哪些呢?
常用格式符号
这里我们直接用一段代码来直观的观察 DecimalFormat.format() 常用格式化符号,以及格式化符号格式化数据后的输出效果,示例代码如下
public static void main(String[] args) { double num = 1234.5678; // # - 数字占位符,没有数字时不显示 DecimalFormat df1 = new DecimalFormat("###.##"); // 1234.57 System.out.println("###.##:"+df1.format(num)); // 0 - 数字占位符,没有数字时显示0 DecimalFormat df2 = new DecimalFormat("00000.000"); // 01234.568 System.out.println("00000.000:"+df2.format(num)); // . - 小数点 DecimalFormat df3 = new DecimalFormat("#.##"); // 1234.57 System.out.println("#.##"+df3.format(num)); // , - 千位分隔符 DecimalFormat df4 = new DecimalFormat("#,###.##"); // 1,234.57 System.out.println("#,###.##"+df4.format(num)); // % - 百分比 DecimalFormat df5 = new DecimalFormat("#.##%"); // 45.67% System.out.println("#.##%"+df5.format(0.4567)); // E - 科学计数法 DecimalFormat df6 = new DecimalFormat("0.00E00"); // 1.23E06 System.out.println("0.00E00"+df6.format(1234567)); // 货币符号(需要设置货币) DecimalFormat df7 = new DecimalFormat("¤#,##0.00"); df7.setCurrency(java.util.Currency.getInstance("USD")); // $1,234.56 System.out.println("¤#,##0.00"+df7.format(1234.56)); }
执行后的结果返回如图
重要方法详解
这里我们需要用到 DecimalFormat.parse() 。DecimalFormat.parse() 是 Java 中 DecimalFormat 类的一个方法,用于将符合特定格式的字符串(例如带有千位分隔符、货币符号或百分比的数字文本)解析并转换为对应的数值对象(如 Number 或其子类 Double、Long 等),它会根据格式规则忽略非数字字符并处理本地化的数字表示,如果字符串不符合格式要求则可能抛出 ParseException 异常。示例代码如下
public static void main(String[] args) throws Exception { DecimalFormat df = new DecimalFormat("#,##0.00"); // 1. parse(String text) - 返回Number对象 String str = "1,234.56"; Number number = df.parse(str); // 1234.56 System.out.println(number.doubleValue()); // 2. parse(String text, ParsePosition pos) ParsePosition pos = new ParsePosition(0); Number result = df.parse("123,456.78元", pos); // 123456.78 System.out.println(result); System.out.println("解析结束位置: " + pos.getIndex()); // 10 }
执行结果如图
DecimalFormat.format() 同时支持对数字四舍五入,通过 df.setRoundingMode(RoundingMode.CEILING); 设置,目前支持的四舍五入的方式包括以下几种
不同的舍入模式对应返回结果也不相同,可以通过源码的注释来有个大概的了解
使用注意
在使用 DecimalFormat.format() 还需要注意以下几种情况,防止导致不必要的报错。
入参判断
就像我们上面遇到的情况,就是传入了DecimalFormat.format() 不支持的 String 数字导致的格式化异常,那么为了保证代码的健壮性,我们就可以加入以下的参数校验
public static void main(String[] args) throws Exception{ DecimalFormat decimalFormat = new DecimalFormat("###,##0.00"); Object a = "11111"; // 检查 a 是否为 null if (a == null) { System.out.println("输入值不能为 null"); return; // 或者提供默认值,如 a = "0"; } //判断 a 是否是字符串,字符串需要先转为数字 try { if (a instanceof String) { BigDecimal bigDecimal = new BigDecimal((String) a); String format = decimalFormat.format(bigDecimal); System.out.println("string"+format); } else { String format = decimalFormat.format(a); System.out.println(format); } } catch (NumberFormatException e) { System.out.println("输入字符串不是有效的数字格式"); } }
这样可以保证在格式化过程中不会遇到非可控的报错了。
线程安全性
DecimalFormat 不是线程安全的。根据其官方文档明确说明,DecimalFormat 的格式化和解析方法都不是同步的,如果在多线程环境中共享同一个实例而不采取额外的同步措施,可能会导致数据错误或解析异常。为了在多线程场景下安全使用,建议为每个线程创建独立的 DecimalFormat 实例,或使用 ThreadLocal 进行包装,正确的使用方式如下
// DecimalFormat 不是线程安全的! // 错误示例: public class UnsafeExample { private static final DecimalFormat df = new DecimalFormat("#.##"); public static String formatNumber(double num) { return df.format(num); // 多线程下可能出错 } } // 正确做法1:每次创建新实例 public class SafeExample1 { public static String formatNumber(double num) { DecimalFormat df = new DecimalFormat("#.##"); return df.format(num); } } // 正确做法2:使用ThreadLocal public class SafeExample2 { private static final ThreadLocal<DecimalFormat> threadLocalFormat = ThreadLocal.withInitial(() -> new DecimalFormat("#.##")); public static String formatNumber(double num) { return threadLocalFormat.get().format(num); } }
特殊字符转义
当需要对格式化后的数据增加特殊字符时,可以通过以下的方式转义特殊字符,示例代码如下
public static void main(String[] args) { double num = 1234.56; // 如果模式中包含特殊字符,需要使用单引号转义 DecimalFormat df1 = new DecimalFormat("'$'#,##0.00"); // $1,234.56 System.out.println(df1.format(num)); // 显示井号字符 DecimalFormat df2 = new DecimalFormat("'#'#"); // #1235 System.out.println(df2.format(num)); }
执行结果如图所示
性能考虑
当遇到需要多多数据进行格式转换时,需要避免在循环中来循环创建销毁实例,示例代码如下
public class PerformanceExample { // 避免在循环中重复创建DecimalFormat public static void badPractice(double[] numbers) { for (double num : numbers) { // 每次都创建新实例 DecimalFormat df = new DecimalFormat("#.##"); System.out.println(df.format(num)); } } // 推荐做法:复用实例 public static void goodPractice(double[] numbers) { DecimalFormat df = new DecimalFormat("#.##"); for (double num : numbers) { System.out.println(df.format(num)); } } }
常见问题处理
在使用的过程中,会遇到一些特殊的场景,对于这些场景数据,也可以通过下面的方式处理
public static void main(String[] args) { // 问题1:格式化整数时显示小数位 DecimalFormat df1 = new DecimalFormat("#.00"); // 100.00 System.out.println(df1.format(100)); // 解决方案:根据需要调整模式 DecimalFormat df2 = new DecimalFormat("#"); // 100 System.out.println(df2.format(100.00)); // 问题2:负数格式化 DecimalFormat df3 = new DecimalFormat("#.##"); // -123.46 System.out.println(df3.format(-123.456)); // 解决方案:分别指定正负数格式 DecimalFormat df4 = new DecimalFormat("#.##;(#.##)"); // (123.46) System.out.println(df4.format(-123.456)); // 问题3:超大/超小数字 double hugeNum = 1.23456789E30; DecimalFormat df5 = new DecimalFormat("#,##0.00"); //1,234,567,890,000,000,000,000,000,000,000.00 System.out.println(df5.format(hugeNum)); // 解决方案:使用科学计数法 DecimalFormat df6 = new DecimalFormat("0.00E0"); // 1.23E30 System.out.println(df6.format(hugeNum)); }
执行结果如图所示
总的来说
DecimalFormat.format() 是 Java 中用于数字格式化的核心方法,它能将数字按照指定模式(如小数位数、千位分隔符、百分比、货币符号等)转换为格式化的字符串,支持舍入控制和本地化显示,是处理数字显示需求的强大工具。
DecimalFormat.format() 的主要特点包括:格式高度可定制,通过模式字符串灵活控制小数位、千位分隔等显示格式;支持本地化,能根据不同地区适配数字格式;提供舍入控制,可配置多种舍入规则;类型兼容性好,支持多种数值类型格式化;但非线程安全,在多线程环境下需要额外处理同步问题。