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