都是 0,为什么 BigDecimal.ZERO 和 0.00 比较竟然是 false?



那天早上,我照例去公司楼下买咖啡。我掏出手机,对老板说:"老板,我钱包里还有 0 块钱,能不能先赊一杯?"

老板瞄了一眼我的余额页面:

  • 页面一:余额:0
  • 页面二:余额:0.00

老板一脸严肃:"小伙子,你这两个不一样。",我当场愣住。"0 不就是 0 吗?你这不是数学问题,是态度问题吧?"

老板慢悠悠地说了一句让我后来在代码里反复咀嚼的话: "数值一样,不代表记账方式一样。"

那一刻,我仿佛听见了 BigDecimal 在我耳边冷笑。

问题重现:一行代码引发的血案

我们先来看那段"罪魁祸首"代码。

输出结果是:

是不是很反直觉?

"都是 0 啊兄弟,你俩怎么就不能好好相处?"

别急,Java 没疯,是我们低估了 BigDecimal。

BigDecimal 到底在"较真"什么?

BigDecimal 不只是"值",还有"刻度"

BigDecimal 内部,其实由两部分组成:

  • unscaledValue(未缩放的整数值)
  • scale(小数位数)

我们拆开来看:

等价于:

而:

等价于:

数值相同,但小数位精度不同。这就好比:

  • 0:口袋里没钱
  • 0.00:钱包里有两位小数精度的余额,只是刚好是零

在财务系统眼里,这俩可不是一回事。

equals 为啥这么"死板"?

我们直接看源码(简化版)。

划重点:BigDecimal 的 equals,既比较值,也比较 scale。

所以:

  • 0(scale=0)
  • 0.00(scale=2)
  • scale 不同,equals = false

Java 在这里的设计理念只有一句话: "你既然用 equals,那我就精确到每一位给你算。"

Objects.equals 并没有"背锅"

很多同学第一反应是:"是不是 Objects.equals 有问题?"

其实完全不是。

Objects.equals 本质上只是:

  • 帮你处理 null
  • 最终还是调用 a.equals(b)

所以真正的"裁判",从头到尾都是 BigDecimal 的 equals。

一个更扎心的对照实验

我们来做一组对比。

但如果换一种方式:

是不是瞬间清醒了?

equals vs compareTo,本质差异在哪?

我给你整理了一张"面试必背级别"的表。

一句话总结:equals 是"格式敏感型选手",compareTo 是"结果导向型选手"。

生活类比:记账本 vs 心算

把 BigDecimal 想成两种人:

  • equals:会计
  • "你给我的是 0 还是 0.00?凭证不一样,我就不认。"
  • compareTo:老板
  • "别跟我扯格式,你就说是不是零。"

业务判断 里,你通常更像老板;在数据结构(Set / Map) 里,Java 更像会计。

HashMap 里的"隐形大坑"

如果你用 BigDecimal 作为 Map 的 key:

结果是:

这意味着什么?在 HashMap 眼里,0 和 0.00 是两个完全不同的 key。

如果这是:

  • 金额缓存
  • 财务统计
  • 对账系统

那基本就是线上事故预备役

正确姿势一:统一 scale

在进入 Map / Set 之前,先"洗一遍数据"。

示例:

正确姿势二:业务判断永远用 compareTo

这是我在代码评审里见一次夸一次的写法。

面试官最爱问的追问

Q:那为什么 BigDecimal 不在 equals 里忽略 scale?

这是个设计哲学问题。原因有三点:

  1. 精度本身就是信息
  2. 财务、统计、科学计算场景必须严谨
  3. equals 一旦改规则,会破坏 HashMap、HashSet 的一致性

Java 的选择是: "我不替你做业务判断,我只保证对象语义一致。"

终极对照表(建议收藏)

一句话总结(可以直接背)

BigDecimal 的 equals 比的是"值 + 精度",compareTo 比的是"数值大小",业务判断永远优先用 compareTo。

END

写到这里,我又想起那家咖啡店老板。在生活里:0 就是没钱。但在代码里:0 是 0,0.00 是 0.00,你要说清楚你想比什么。

BigDecimal 从来不坑你,它只是比你想得更认真一点。

如果你觉得这篇文章帮你少踩了一个坑,欢迎点个 "在看"、转发给你的好朋友。

我是小米,一个喜欢分享技术的31岁程序员。如果你喜欢我的文章,欢迎关注我的微信公众号"软件求生",获取更多技术干货!

相关推荐
yaoxin52112312 分钟前
470. Java 反射 - Member 接口与 AccessFlag
java·开发语言·python
做个文艺程序员29 分钟前
Linux第24篇:Java应用监控体系搭建:Prometheus+Grafana可视化运维
java·grafana·prometheus
FF2501_940228581 小时前
Grid 构建月历网格:7 列模板 + 日期占位算法
后端·华为·harmonyos·鸿蒙系统
神奇小汤圆1 小时前
线程池的 DiscardPolicy 为什么是「静默杀手」?一个生产事故的完整复盘
后端
大鸡腿同学1 小时前
个人博主 AI 剪辑实操:口播视频 3 步出片,省 80% 剪辑时间
后端
小钻风33661 小时前
Spring Boot 文件上传详解:深入理解 MultipartFile 的使用与原理
java·开发语言
其美杰布-富贵-李1 小时前
Spring Boot 工程开发全流程说明
java·spring boot·后端
xianjixiance_2 小时前
HarmonyOS应用开发实战:萌宠日记 - 整体架构设计与技术选型解析
后端
掘金者阿豪2 小时前
数据库优化器到底在想什么:一个SQL今天能跑明天崩的背后
后端
b130538100492 小时前
HarmonyOS应用开发实战:萌宠日记 - 回调函数模式
后端