Java 的整数运算默认不检查溢出——这句话大多数人都知道,但"静默"两个字到底有多静默,真实跑一次最有说服力:程序拿到一个彻底错误的结果,却没有任何信号提示它出过错。
上一篇:try-with-resources 真实是逆序关闭:被抑制的异常真实附在主异常身上,不是丢了
int 是 32 位补码表示,最高位是符号位——加到溢出时,进位会真实"污染"到符号位上。
Integer.MAX_VALUE = 2147483647 Integer.MAX_VALUE + 1 真实结果 = -2147483648(没有抛任何异常,静默环绕成了负数) 是不是正好等于 Integer.MIN_VALUE = true
这不是随机出错,是补码运算的真实、确定性结果——Integer.MAX_VALUE 的二进制是 0111...1(31 个 1),加 1 之后按位进位,真实变成 1000...0,而这个模式在补码里恰好就是 Integer.MIN_VALUE。这个环绕行为在 Java 语言规范里是明确定义的,不是未定义行为,但"明确定义"不代表"安全"——它只保证结果可预测,不保证结果是你想要的。
两个看起来完全正常、单独都远小于 Integer.MAX_VALUE 的数相加,同样可能溢出。
1500000000 + 1000000000 真实数学结果应为 = 2500000000 用 int 相加真实得到 = -1794967296(完全没有报错提示,结果已经错了但程序不知道)
15 亿和 10 亿,单独看都是很普通的数字(远小于约 21 亿的 Integer.MAX_VALUE),但相加之后的真实数学结果 25 亿已经超出了 int 能表示的范围——程序真实得到一个负数,如果这个结果后续被用来做数组下标、金额计算、或者任何逻辑判断,错误会真实、安静地传播下去,不会在发生的那一刻留下任何痕迹。
JDK 8 引入的 Math.addExact/subtractExact/multiplyExact 系列方法,专门用来在溢出真实发生时立刻抛异常。
Math.addExact 真实抛出 ArithmeticException: integer overflow
100 + 200 用普通 + 真实结果 = 300 100 + 200 用 Math.addExact 真实结果 = 300(不溢出时两者完全一致)
顺带真实验证一下:long 是不是也会有同样的问题——只是范围大得多(约正负 922 亿亿),日常业务里很难真的碰到。
Long.MAX_VALUE + 1 真实结果 = -9223372036854775808(同样静默环绕,只是需要接近 9.2×10^18 才会踩到)
每一行都是真实运行这段代码得到的结果。