跳转到主内容
趣航编程网 - 趣学编程,启航技术之路!

如何防范在 long 类型累加计算中由于数值溢出翻转为负数的逻辑 Bug

long相加溢出会绕回为负数,因Java用补码实现静默翻转;可用Math.addExact捕获异常或BigInteger无界计算;排序时误转long为int会导致逻辑反转。 long 相加时为什么会翻转成负数 Java 的
long
类型是 64 位有符号整数,范围是 -9223372036854775808 到 9223372036854775807。超出这个边界后不会报错,而是按补码规则“绕回”——比如
Long.MAX_VALUE + 1L
得到的是
Long.MIN_VALUE
,表现为一个巨大负数。这种静默翻转在累加场景中极难察觉,尤其当数据来自外部(如日志行数、计费金额、时间差累计)时,可能只在极端 case 下暴露。 用 Math.addExact 捕获溢出并抛异常 Java 8+ 提供了带溢出检查的数学工具方法,比手动判断边界更可靠:
Math.addExact(a, b)
在溢出时直接抛
ArithmeticException
,强制你处理异常路径 适用于明确不允许丢失精度或错误容忍度低的业务,比如金融结算、配额校验 注意:它不支持
long
与
int
混合运算,参数必须同为
long
;若一方是
int
,需显式转成
long
示例:
try { long total = 0L; for (long value : values) { total = Math.addExact(total, value); // 任一溢出立刻中断 } } catch (ArithmeticException e) { throw new IllegalStateException("累加溢出,当前 total=" + total + ", 新值=" + value, e); }
用 BigInteger 做无界累加 当业务逻辑本身允许任意大的数值(如统计 PV、生成唯一序列号),且你不想被溢出打断流程,
BigInteger
是最稳妥的选择: 没有固定上限,内存够就能算 性能比
long
差不少,但对非高频路径(如后台批处理、配置校验)可接受 注意不要在循环内反复创建
BigInteger
实例;复用
BigInteger.ZERO
起始,用
add()
累积 示例:
BigInteger total = BigInteger.ZERO; for (long value : values) { total = total.add(BigInteger.valueOf(value)); }
排序比较中误 cast long 为 int 导致翻转 这是最隐蔽也最常踩的坑:在
Comparator
中直接写
(int)(a - b)
,而 a、b 是
long
时间戳或 ID。一旦差值超过
Integer.MAX_VALUE
(约 21 亿),强转后就是负数,排序逻辑彻底反转。 正确做法是用
Long.compare(a, b)
,它内部通过条件分支避免减法 或者改用 lambda 返回
Long.signum(a - b)
,但要注意
a - b
本身仍可能溢出(仅当 a、b 符号相反且绝对值极大时) 更安全的是:先比较符号,再比较绝对值,但通常
Long.compare
已足够 错误写法:
return (int)(left.getGmtCreate().getTime() - right.getGmtCreate().getTime()); // ⚠️ 溢出即翻转
正确写法:
return Long.compare(left.getGmtCreate().getTime(), right.getGmtCreate().getTime());
真正麻烦的不是“会不会溢出”,而是“溢出后行为是否符合语义”。比如排序翻转是静默错,而累加翻转可能让余额变负——后者容易被监控捕获,前者往往要靠用户投诉才发现。

相关文章