导读:本期聚焦于台湾程序员创作的《在Java中数值计算异常如何避免_JavaArithmeticException解析》,敬请观看详情。程序运行时报ArithmeticException通常是因为除数为零或者取模运算的除数不合法,这类异常属于运行时异常,编译器不会提前提醒,只有在代码真正执行到那一行才会暴露出来。本文从ArithmeticException的产生原理讲起,分析整数除法与浮点数除法在除零行为上的本质区别,给出多种有效的防御性编码方案,包括运算前的条件判断、BigInteger的适用场景以及捕获异常的正确姿势,帮助开发者写出更健壮的数值计算代码。

ArithmeticException是Java中最常见的运行时异常之一,它继承自RuntimeException,典型的触发场景就是整数除以零。很多初学者第一次遇到这个异常时往往会感到困惑,明明代码编译通过了,为什么运行的时候还是崩了。这是因为编译器只负责语法检查,无法预知程序运行时变量的实际取值。想要彻底避免这类问题,需要从异常的产生机制、防御性编码以及异常捕获三个层面入手。

在Java中数值计算异常如何避免_JavaArithmeticException解析

ArithmeticException产生的根本原因

在Java的数值体系里,整数运算和浮点运算对除零的处理方式完全不同。当int或long类型的除数为零时,JVM会直接抛出ArithmeticException,因为数学上整数除零没有定义结果,硬件层面也没有对应的表示方式。而浮点数遵循IEEE 754标准,除以零会得到Infinity或者NaN这样的特殊值,不会抛异常。看下面的代码示例:

public class DivideDemo {
    public static void main(String[] args) {
        int a = 10;
        int b = 0;
        // 整数除零,抛出ArithmeticException: / by zero
        System.out.println(a / b);

        double x = 10.0;
        double y = 0.0;
        // 浮点除零,输出Infinity,不抛异常
        System.out.println(x / y);
    }
}

除了除零之外,取模运算同样会触发这个异常。对零取模在数学上同样无意义,JVM的处理方式与除零一致。还有一种容易被忽略的场景是BigInteger的除法运算,当调用divide方法且除数为ZERO时,也会抛出ArithmeticException,错误信息同样提示除以零。此外,某些超出精度要求的操作也会抛出这个异常,比如BigDecimal在除法运算结果无法精确表示且没有指定舍入模式时,同样会抛出ArithmeticException,这种情况在金额计算中尤其常见。

import java.math.BigDecimal;
import java.math.BigInteger;

public class OtherCases {
    public static void main(String[] args) {
        BigInteger big = new BigInteger("100");
        // BigInteger除以零也会抛ArithmeticException
        System.out.println(big.divide(BigInteger.ZERO));

        BigDecimal d1 = new BigDecimal("10");
        BigDecimal d2 = new BigDecimal("3");
        // 10除以3是无限循环小数,未指定精度会抛异常
        System.out.println(d1.divide(d2));
    }
}

运算前的条件判断是首选方案

避免ArithmeticException最直接有效的办法,就是在执行除法运算之前先判断除数是否合法。这种防御性检查成本极低,一次比较操作几乎不影响性能,却能从根源上杜绝异常的发生。实际项目中,除数往往来自外部输入、数据库字段或者上游服务的返回值,开发阶段很难保证这些值一定不为零,主动校验是必要的。

public class SafeDivide {
    public static int safeDivide(int dividend, int divisor) {
        if (divisor == 0) {
            // 除数为零时返回默认值或抛出业务异常
            throw new IllegalArgumentException("除数不能为零");
        }
        return dividend / divisor;
    }

    public static void main(String[] args) {
        try {
            System.out.println(safeDivide(100, 5));
            System.out.println(safeDivide(100, 0));
        } catch (IllegalArgumentException e) {
            System.out.println("参数校验失败: " + e.getMessage());
        }
    }
}

对于BigDecimal的精度问题,解决方案是在调用divide方法时显式指定精度和舍入模式。金额计算中推荐使用RoundingMode.HALF_UP也就是四舍五入,并明确指定保留的小数位数。这样既避免了异常,也保证了计算结果符合业务预期。

import java.math.BigDecimal;
import java.math.RoundingMode;

public class BigDecimalDemo {
    public static void main(String[] args) {
        BigDecimal d1 = new BigDecimal("10");
        BigDecimal d2 = new BigDecimal("3");
        // 指定保留两位小数,四舍五入
        BigDecimal result = d1.divide(d2, 2, RoundingMode.HALF_UP);
        System.out.println(result); // 输出3.33
    }
}

值得注意的是,使用new BigDecimal(0.1)这种传入double的构造方式会带来精度陷阱,因为double本身就无法精确表示0.1,构造出来的BigDecimal会带有很长的尾巴。正确做法是使用字符串构造或者BigDecimal.valueOf方法,从源头上保证数值的精确性。

异常捕获与业务场景的结合

并不是所有场景都能在运算前完成校验,比如除数来自复杂的异步计算链条,校验点和运算点分离,这时捕获异常就成了兜底手段。捕获ArithmeticException时要注意,try块的范围应该尽量小,只包裹可能抛异常的那一行运算代码,避免掩盖其他真正的程序缺陷。

public class CatchDemo {
    public static void main(String[] args) {
        int[] data = {100, 200, 0, 300};
        int total = 500;

        for (int divisor : data) {
            try {
                // 只包裹可能出错的运算行
                int avg = total / divisor;
                System.out.println("平均值: " + avg);
            } catch (ArithmeticException e) {
                // 除数为零时的降级处理
                System.out.println("除数为" + divisor + ",跳过该组计算");
            }
        }
    }
}

关于异常捕获和条件判断该如何选择,业界的共识是:能预判且属于正常业务分支的情况用条件判断,即所谓EAFP与LBYL之争中的LBYL思路;而异常本身发生概率极低且属于意外情况时,用try-catch兜底更合适。频繁抛出和捕获异常的性能开销远大于一次简单的if判断,在循环或高频调用的代码路径中尤其要避免用异常控制流程。

实际项目中的综合防御策略

在一个真实的业务系统里,数值计算往往散落在多个层次,比如订单金额分摊、库存均分、统计报表等场景。单一手段难以覆盖所有情况,建议建立分层防御体系。首先是入口层校验,所有外部传入的数值参数在进入业务逻辑前统一校验,非法值直接拒绝。其次是计算层封装,把除法这类危险运算封装成工具方法,统一处理零值和精度问题,避免每个开发人员各自为战。

import java.math.BigDecimal;
import java.math.RoundingMode;

public class MathUtils {
    /**
     * 安全除法:除数为零时返回默认值
     */
    public static BigDecimal divide(BigDecimal a, BigDecimal b, BigDecimal defaultVal) {
        if (a == null || b == null || BigDecimal.ZERO.compareTo(b) == 0) {
            return defaultVal;
        }
        return a.divide(b, 4, RoundingMode.HALF_UP);
    }

    public static void main(String[] args) {
        // 典型场景:计算占比,分母为零时返回零
        BigDecimal ratio = MathUtils.divide(
                new BigDecimal("30"), new BigDecimal("0"), BigDecimal.ZERO);
        System.out.println("占比: " + ratio);
    }
}

最后是测试层面的保障,针对数值计算相关的代码,单元测试必须覆盖除数为零、极端大数、负数取模等边界情况。数值类型的溢出问题也值得关注,Math.addExact和Math.multiplyExact这类方法在溢出时会抛出ArithmeticException,这其实是一种把隐患提前暴露的设计思路。配合长时间的线上监控,通过日志统计异常发生的位置和频率,可以持续发现系统中的薄弱环节。把校验、封装、测试和监控结合起来,ArithmeticException就不再是线上事故的常客了。

Java ArithmeticException 异常处理修改时间:2026-09-13 14:11:26

免责声明:已尽一切努力确保本网站所含信息的准确性。网站作品多为原创整理与精心创作,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们进行处理Email:chomcom@qq.com。
引用或转载本作品时,请注明当前出处:https://www.ipipp.com/html/20260913/56051.html,基于非商业用途的前提下,欢迎转载或二创本作品。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。