java.lang.IllegalArgumentException: Cannot format given Object as a Number

简介: 本文详解 Java 中 DecimalFormat.format() 的使用方法与注意事项,涵盖格式化模式、舍入规则、线程安全、特殊字符转义及常见问题处理,提升代码健壮性与性能。

背景信息

最近在一次使用历史功能导出表格的时候,出现了一个格式转换的报错,具体的报错信息如下

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);
    }
}

执行后的输出结果如图

image.png

那么 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));
    }

执行后的结果返回如图

image.png

重要方法详解

这里我们需要用到 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
    }

执行结果如图

image.png

DecimalFormat.format() 同时支持对数字四舍五入,通过 df.setRoundingMode(RoundingMode.CEILING); 设置,目前支持的四舍五入的方式包括以下几种

image.png

不同的舍入模式对应返回结果也不相同,可以通过源码的注释来有个大概的了解

image.png

使用注意

在使用 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));
    }


执行结果如图所示

image.png

性能考虑

当遇到需要多多数据进行格式转换时,需要避免在循环中来循环创建销毁实例,示例代码如下

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));  
    }


执行结果如图所示

image.png

总的来说

DecimalFormat.format() 是 Java 中用于数字格式化的核心方法,它能将数字按照指定模式(如小数位数、千位分隔符、百分比、货币符号等)转换为格式化的字符串,支持舍入控制和本地化显示,是处理数字显示需求的强大工具。

DecimalFormat.format() 的主要特点包括:格式高度可定制,通过模式字符串灵活控制小数位、千位分隔等显示格式;支持本地化,能根据不同地区适配数字格式;提供舍入控制,可配置多种舍入规则;类型兼容性好,支持多种数值类型格式化;但非线程安全,在多线程环境下需要额外处理同步问题。






相关文章
|
7天前
|
数据采集 人工智能 安全
|
17天前
|
云安全 监控 安全
|
3天前
|
机器学习/深度学习 人工智能 前端开发
构建AI智能体:七十、小树成林,聚沙成塔:随机森林与大模型的协同进化
随机森林是一种基于决策树的集成学习算法,通过构建多棵决策树并结合它们的预测结果来提高准确性和稳定性。其核心思想包括两个随机性:Bootstrap采样(每棵树使用不同的训练子集)和特征随机选择(每棵树分裂时只考虑部分特征)。这种方法能有效处理大规模高维数据,避免过拟合,并评估特征重要性。随机森林的超参数如树的数量、最大深度等可通过网格搜索优化。该算法兼具强大预测能力和工程化优势,是机器学习中的常用基础模型。
285 164
|
2天前
|
机器学习/深度学习 自然语言处理 机器人
阿里云百炼大模型赋能|打造企业级电话智能体与智能呼叫中心完整方案
畅信达基于阿里云百炼大模型推出MVB2000V5智能呼叫中心方案,融合LLM与MRCP+WebSocket技术,实现语音识别率超95%、低延迟交互。通过电话智能体与座席助手协同,自动化处理80%咨询,降本增效显著,适配金融、电商、医疗等多行业场景。
291 155
|
4天前
|
机器学习/深度学习 人工智能 前端开发
构建AI智能体:六十九、Bootstrap采样在大模型评估中的应用:从置信区间到模型稳定性
Bootstrap采样是一种通过有放回重抽样来评估模型性能的统计方法。它通过从原始数据集中随机抽取样本形成多个Bootstrap数据集,计算统计量(如均值、标准差)的分布,适用于小样本和非参数场景。该方法能估计标准误、构建置信区间,并量化模型不确定性,但对计算资源要求较高。Bootstrap特别适合评估大模型的泛化能力和稳定性,在集成学习、假设检验等领域也有广泛应用。与传统方法相比,Bootstrap不依赖分布假设,在非正态数据中表现更稳健。
221 113
|
10天前
|
SQL 自然语言处理 调度
Agent Skills 的一次工程实践
**本文采用 Agent Skills 实现整体智能体**,开发框架采用 AgentScope,模型使用 **qwen3-max**。Agent Skills 是 Anthropic 新推出的一种有别于mcp server的一种开发方式,用于为 AI **引入可共享的专业技能**。经验封装到**可发现、可复用的能力单元**中,每个技能以文件夹形式存在,包含特定任务的指导性说明(SKILL.md 文件)、脚本代码和资源等 。大模型可以根据需要动态加载这些技能,从而扩展自身的功能。目前不少国内外的一些框架也开始支持此种的开发方式,详细介绍如下。
764 5