NumberFormatException是Java中最常见的运行时异常之一,它继承自IllegalArgumentException,属于非受检异常。只要把一个不符合数字格式的字符串传给Integer.parseInt、Long.parseLong、Double.parseDouble或者构造器new Integer(String)这类方法,虚拟机就会立刻抛出这个异常。很多开发者觉得它只是个小问题,但在真实的业务系统里,用户输入、接口参数、Excel导入、配置文件读取等环节都可能埋着这个坑,一旦触发轻则功能报错,重则整个任务中断。想彻底解决它,需要先弄清楚异常到底在哪里、为什么被抛出。

异常产生的底层原理与源码分析
以最常用的Integer.parseInt(String s)为例,JDK内部的处理逻辑大致分为三步:先判断字符串是否为null,再逐字符检查是否都是数字(允许开头一个正负号),最后把字符按位累加成int值。任何一步不满足条件,都会走到显式的throw new NumberFormatException语句。
查看JDK源码可以明显看到两个抛出点。第一个是入参为null时:
public static int parseInt(String s) throws NumberFormatException {
// 字符串为null直接抛异常,提示信息为 null
if (s == null) {
throw new NumberFormatException("null");
}
// 后续解析逻辑省略
}第二个是循环逐个字符解析时,一旦遇到不在0到9范围内的字符,或者最终累加的结果超出了int的表示范围,就会抛出带具体错误信息的异常。例如传入字符串"123a",解析到字符a时失败,异常信息会包含For input string: "123a"这样的内容,这个提示信息对定位问题非常有用,看日志时可以直接确认是哪个字符串出了问题。
理解了原理就明白,这个异常本质上不是程序bug,而是方法设计者主动用它来告诉你:传进来的字符串根本不是合法数字。所以解决思路不是屏蔽异常,而是保证转换前数据合法,或者在转换失败时给出合理的降级处理。
常见的触发场景逐一排查
第一种也是最高频的场景是空字符串。用户在表单里没填内容直接提交,后端拿到的是长度为0的字符串,直接调用Integer.parseInt("")必然报错。第二种是首尾空格或不可见字符,比如" 100",肉眼看着没问题,但第一个字符是空格,解析直接失败。第三种是全角数字或全角负号,从网页复制内容时尤其常见,全角字符的编码值与半角数字完全不同。
第四种是小数点或科学计数法的问题。用Integer.parseInt("3.14")会抛异常,因为int不接受小数点;反过来,某些场景下字符串里混入了逗号分隔符,例如"1,000",同样会解析失败。第五种是数值超出目标类型的范围,Long.parseLong("99999999999999999999")虽然每个字符都是数字,但结果超过long的上限,同样抛NumberFormatException。
String[] inputs = {"", " 100", "123", "3.14", "1,000", "99999999999999999999"};
for (String s : inputs) {
try {
long value = Long.parseLong(s);
System.out.println("解析成功: " + value);
} catch (NumberFormatException e) {
System.out.println("解析失败: [" + s + "],原因: " + e.getMessage());
}
}第六种场景容易被忽视:字符串看起来完全是数字,但编码里混入了零宽字符或者从CSV、Excel导出的数据带有前后不可见的换行符\r\n。这类问题用肉眼看不出来,打印s.length()和每个字符的编码值才能发现端倪。
五种实用的解决方案与最佳实践
最基础的方案是转换前做校验。先用StringUtils.isBlank判断是否为空,再用trim去掉首尾空格,必要时用正则表达式预检格式。例如正则^-?\d+$可以匹配带可选负号的纯整数,校验通过再调用转换方法,能拦住绝大多数非法输入。
public static Integer safeParseInt(String s) {
if (s == null) {
return null;
}
String trimmed = s.trim();
if (!trimmed.matches("-?\\d+")) {
return null;
}
try {
return Integer.parseInt(trimmed);
} catch (NumberFormatException e) {
// 兜底捕获,防止数值超出int范围
return null;
}
}第二个方案是使用异常捕获做降级处理。在无法预知输入质量的场景,比如读取外部文件或第三方接口返回值,用try-catch包裹转换逻辑,失败时返回默认值或记录日志,保证主流程不中断。要注意catch的范围尽量精确到NumberFormatException,不要图省事直接捕获Exception,否则会掩盖其他真正的bug。
第三个方案是借助成熟工具类。Apache Commons Lang的NumberUtils.toInt(String str, int defaultValue)和NumberUtils.createInteger封装了空值判断和异常处理,一行代码就能完成安全转换。如果项目里有用户输入校验需求,Spring框架的@Pattern、@Digits注解配合Validation可以在入口层就把非法参数挡在业务逻辑之外,从源头上减少异常发生的机会。
第四个方案针对特殊格式数据做预处理。遇到带千分位逗号的字符串,先执行replace(",", "");遇到全角数字,可以先做全角转半角处理;遇到可能超出int范围的数字,改用Long.parseLong或者new BigDecimal(String),BigDecimal能处理任意长度的数字且精度可控,特别适合金额类业务。
最后一个建议是养成看异常信息的习惯。NumberFormatException的message里会打印出引发问题的原始字符串,配合异常堆栈中的行号,通常几秒钟就能定位到出问题的代码位置。定位到之后再分析数据来源,是前端没校验、用户手工输入还是数据文件本身脏了,从数据源头修复往往比在代码里层层打补丁更彻底。把这些实践组合起来,数字转换相关的线上告警基本可以降到很低的水平。
NumberFormatException数字格式化异常Java异常处理修改时间:2026-09-07 04:26:30