java.lang.IllegalArgumentException: Cannot format given Object as a Number
java.lang.IllegalArgumentException: Cannot format given Object as a Number
背景信息
最近在一次使用历史功能导出表格的时候,出现了一个格式转换的报错,具体的报错信息如下
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() 的主要特点包括:格式高度可定制,通过模式字符串灵活控制小数位、千位分隔等显示格式;支持本地化,能根据不同地区适配数字格式;提供舍入控制,可配置多种舍入规则;类型兼容性好,支持多种数值类型格式化;但非线程安全,在多线程环境下需要额外处理同步问题。
更多推荐




所有评论(0)