为什么阿里巴巴禁止使用BigDecimal的equals方法做等值比较

本文分析了为什么阿里巴巴禁止使用BigDecimal的equals方法进行等值比较,通过错误示例和代码测试展示了equals方法会同时比较值和精度。推荐使用compareTo方法仅比较数值。了解BigDecimal的不同构造方法对精度的影响,以避免精度导致的比较错误。

BigDecimal介绍

BigDecimal,相信对于很多人来说都不陌生,很多人都知道他的用法,这是一种java.math包中提供的一种可以用来进行精确运算的类型。

在这里插入图片描述

很多人都知道,在进行金额表示、金额计算等场景,不能使用double、float等类型,而是要使用对精度支持的更好的BigDecimal。

所以,很多支付、电商、金融等业务中,BigDecimal的使用非常频繁。而且不得不说这是一个非常好用的类,其内部自带了很多方法,如加,减,乘,除等运算方法都是可以直接调用的。

除了需要用BigDecimal表示数字和进行数字运算以外,代码中还经常需要对于数字进行相等判断。

《阿里巴巴Java开发手册》的说明

在这里插入图片描述

详细说明

如下代码就是这样的低级错误:

错误一 ==
if(bigDecimal == bigDecimal1){
    // 两个数相等
}

因为BigDecimal是对象,所以不能用==来判断两个数字的值是否相等。

错误二 equals()

但是下面的代码呢?

if(bigDecimal.equals(bigDecimal1)){
    // 两个数相等
}

很失望的告诉你们,得到的结果和你们预想的不一样

简单测试

先来做个实验测试一下:

BigDecimal bigDecimal = new BigDecimal(1);
BigDecimal bigDecimal1 = new BigDecimal(1);
System.out.println(bigDecimal.equals(bigDecimal1));


BigDecimal bigDecimal2 = new BigDecimal(1);
BigDecimal bigDecimal3 = new BigDecimal(1.0);
System.out.println(bigDecimal2.equals(bigDecimal3));


BigDecimal bigDecimal4 = new BigDecimal("1");
BigDecimal bigDecimal5 = new BigDecimal("1.0");
System.out.println(bigDecimal4.equals(bigDecimal5));

以上代码,输出结果为:

true
true
false
BigDecimal的equals原理

在BigDecimal的JavaDoc中其实已经解释了其中原因:

Compares this  BigDecimal with the specified Object for equality.  
Unlike compareTo, this method considers two BigDecimal objects equal only if they are equal in value and scale 
(thus 2.0 is not equal to 2.00 when compared by  this method)

大概意思就是,equals方法和compareTo并不一样,equals方法会比较两部分内容,分别是值(value)和精度(scale)

对应的代码如下:

在这里插入图片描述

所以,我们以上代码定义出来的两个BigDecimal对象(bigDecimal4和bigDecimal5)的精度是不一样的,所以使用equals比较的结果就是false了。

尝试着对代码进行debug,在debug的过程中我们也可以看到bigDecimal4的精度时0,而bigDecimal5的精度是1。

到这里,我们大概解释清楚了,之所以equals比较bigDecimal4和bigDecimal5的结果是false,是因为精度不同

那么,为什么精度不同呢?为什么bigDecimal2和bigDecimal3的精度是一样的(当使用int、double定义BigDecimal时),而bigDecimal4和bigDecimal5却不一样(当使用String定义BigDecimal时)呢?

为什么精度不同 (构造方法)

BigDecimal一共有以下4个构造方法:

BigDecimal(int)
BigDecimal(double) 
BigDecimal(long) 
BigDecimal(String)

以上四个方法,创建出来的的BigDecimal的精度是不同的。

BigDecimal(long) 和BigDecimal(int)

最简单的就是BigDecimal(long) 和BigDecimal(int),因为是整数,所以精度就是0 :

public BigDecimal(int val) {
    this.intCompact = val;
    this.scale = 0;
    this.intVal = null;
}

public BigDecimal(long val) {
    this.intCompact = val;
    this.intVal = (val == INFLATED) ? INFLATED_BIGINT : null;
    this.scale = 0;
}
BigDecimal(double)

对于BigDecimal(double) ,当我们使用new BigDecimal(0.1)创建一个BigDecimal 的时候,其实创建出来的值并不是整好等于0.1的,而是0.1000000000000000055511151231257827021181583404541015625 。这是因为doule自身表示的只是一个近似值。

那么,无论我们使用new BigDecimal(0.1)还是new BigDecimal(0.10)定义,他的近似值都是0.1000000000000000055511151231257827021181583404541015625这个,那么他的精度就是这个数字的位数,即55

其他的浮点数也同样的道理。对于new BigDecimal(1.0)这样的形式来说,因为他本质上也是个整数,所以他创建出来的数字的精度就是0。

所以,因为BigDecimal(1.0)和BigDecimal(1.00)的精度是一样的,所以在使用equals方法比较的时候,得到的结果就是true。

BigDecimal(string)

对于BigDecimal(double) ,当我们使用new BigDecimal(“0.1”)创建一个BigDecimal 的时候,其实创建出来的值正好就是等于0.1的。那么他的精度也就是1。

如果使用new BigDecimal(“0.10000”),那么创建出来的数就是0.10000,精度也就是5。

所以,因为BigDecimal(“1.0”)和BigDecimal(“1.00”)的精度不一样,所以在使用equals方法比较的时候,得到的结果就是false。

如何比较BigDecimal

前面,我们解释了BigDecimal的equals方法,其实不只是会比较数字的值,还会对其精度进行比较。

所以,当我们使用equals方法判断判断两个数是否相等的时候,是极其严格的。

那么,如果我们只想判断两个BigDecimal的值是否相等,那么该如何判断呢?

BigDecimal中提供了compareTo方法,这个方法就可以只比较两个数字的值,如果两个数相等,则返回0。

BigDecimal bigDecimal4 = new BigDecimal("1");
BigDecimal bigDecimal5 = new BigDecimal("1.0000");
System.out.println(bigDecimal4.compareTo(bigDecimal5));

以上代码,输出结果:

0

compareTo源码:

在这里插入图片描述

最佳实践–小数相关操作

当需要使用小数构造一个BigDecimal时,建议使用

String worth = "1.22"
BigDecimal data = new BigDecimal(worth);

一定要谨慎使用Double/double作为参数构建BigDecimal,如下

Double worth = 1.22;
BigDecimal data = new BigDecimal(worth);

两种方式造成结果如下:

public static void main(String[] args) {
        BigDecimal data = new BigDecimal("2");
        Double d = 2.3232;
        // 转成String
        BigDecimal valueWithString = data.multiply(new BigDecimal(d.toString()));
        System.out.println("valueWithString=" + valueWithString.toString());
 
        // 不转String,会使用精度
        BigDecimal valueWithBigDecimal = data.multiply(new BigDecimal(d));
        System.out.println("valueWithBigDecimal=" + valueWithBigDecimal.toString());
}

执行结果为,显然第二种结果不是我们想要的结果。

valueWithString=4.6464
valueWithBigDecimal=4.6463999999999998635757947340607643127441406250

总结

BigDecimal是一个非常好用的表示高精度数字的类,其中提供了很多丰富的方法。

但是,他的equals方法使用的时候需要谨慎,因为他在比较的时候,不仅比较两个数字的值,还会比较他们的精度,只要这两个因素有一个是不相等的,那么结果也是false

如果想要对两个BigDecimal的数值进行比较的话,可以使用compareTo方法。

参考资料

https://juejin.im/post/6874754180058185736

http://www.heartthinkdo.com/?p=829

<think>我们正在讨论为什么Java中不适合使用BigDecimalequals方法进行等值比较。根据提供的引用,我们可以总结出以下关键点: 1. **BigDecimalequals方法不仅比较值,还比较精度(scale)**:也就是说,两个BigDecimal对象如果数值相同但精度不同(比如1和1.0),使用equals方法比较会返回false。 2. **使用compareTo方法则只比较数值**:它会忽略精度的差异,只要数值相等就返回0。 引用中给出了例子: - 使用int或double构造的BigDecimal对象,如果数值相同,equals方法可能返回true(因为精度相同?),但使用String构造时,如果精度不同(如"1"和"1.0"),则equals方法返回false。 - 实际上,使用int构造BigDecimal时,精度为0;使用double构造时,精度会根据double值而定;使用String构造时,精度就是字符串中指定的小数位数。 因此,问题在于:**equals方法要求两个BigDecimal对象不仅数值相等,而且精度(scale)也必须相同**。而compareTo方法则只比较数值(通过转换为相同精度后再比较)。 下面我们详细解释: ### 一、BigDecimalequals方法实现原理 根据Java文档,`BigDecimal`的`equals`方法比较的是: - 数值(经过标准化后的值) - 精度(scale) 例如: ```java BigDecimal a = new BigDecimal("1.0"); // 精度为1 BigDecimal b = new BigDecimal("1.00"); // 精度为2 System.out.println(a.equals(b)); // false,因为精度不同 ``` ### 二、为什么会出现这种差异? 因为`BigDecimal`的设计中,数值由两部分表示: 1. **非标度值(unscaledValue)**:一个`BigInteger`,表示去掉小数点后的整数。 2. **标度(scale)**:小数点后的位数。 例如: - `1.0` 表示为 `unscaledValue=10, scale=1` - `1.00` 表示为 `unscaledValue=100, scale=2` 虽然它们表示的数值都是1,但内部表示不同,所以`equals`方法认为它们不同。 ### 三、正确的等值比较方法 使用`compareTo`方法,因为它只比较数值大小,忽略精度: ```java BigDecimal a = new BigDecimal("1.0"); BigDecimal b = new BigDecimal("1.00"); System.out.println(a.compareTo(b) == 0); // true ``` ### 四、为什么equals方法要这样设计? 根据Java官方文档,`BigDecimal`的`equals`方法应该与`hashCode`方法保持一致。如果两个`BigDecimal`对象在数值和精度上都相等,那么它们的`hashCode`也会相同。如果只比较数值,那么`1.0`和`1.00`的`hashCode`会不同(因为计算方式不同),但是`equals`方法却返回相等,这就违反了`equals`和`hashCode`的约定。 ### 五、示例代码说明 引用[3]中提到的问题: ```java BigDecimal d1 = new BigDecimal(1); // 精度为0 BigDecimal d2 = new BigDecimal(1.0); // 精度为0?注意:使用double构造可能会产生不可预测的精度,但在这个例子中,1.0用double表示是精确的,所以实际上精度为0 BigDecimal s1 = new BigDecimal("1"); // 精度为0 BigDecimal s2 = new BigDecimal("1.0"); // 精度为1 // 使用equals比较 System.out.println(d1.equals(d2)); // true,因为都是精度0,数值1 System.out.println(s1.equals(s2)); // false,因为精度不同(0 vs 1) // 使用compareTo比较 System.out.println(s1.compareTo(s2) == 0); // true ``` 注意:使用`double`构造`BigDecimal`是不推荐的,因为可能会有精度损失。例如: ```java BigDecimal d = new BigDecimal(0.1); // 实际上0.1在double中无法精确表示,所以d的值可能是0.1000000000000000055511151231257827021181583404541015625 ``` ### 六、结论 在比较两个`BigDecimal`是否表示相同的数值时,应该使用`compareTo() == 0`而不是`equals()`,因为: 1. `equals()`方法要求两个对象具有相同的精度和数值,而`compareTo()`只关心数值是否相等。 2. 在金融计算中,我们通常只关心数值是否相等,而不关心精度(例如1元和1.00元是相等的)。 ### 七、最佳实践 1. **避免使用`double`构造`BigDecimal`**:优先使用字符串构造,以避免精度损失。 2. **等值比较使用`compareTo`**: ```java if (a.compareTo(b) == 0) { // 数值相等 } ``` 3. 如果确实需要比较精度(比如在需要精确小数位数的场景),才使用`equals`。 ### 八、相关问题 1. 为什么使用`double`构造`BigDecimal`是不推荐的? 2. 如何正确比较两个`BigDecimal`对象的大小? 3. `BigDecimal`的精度(scale)在计算中是如何影响的? 4. 在HashMap中使用`BigDecimal`作为键时需要注意什么? [^1]: 阿里为什么不能使用 BigDecimalequals 方法等值比较? [^2]: 【Java开发日记】为什么不推荐使用 BigDecimalequals 方法等值比较 ? [^3]: 为什么不能使用 BigDecimalequals 方法等值比较 [^4]: Java开发手册中为什么禁止使用BigDecimalequals方法等值比较以及为什么禁止使用double直接构造 [^5]: 为什么不推荐使用 BigDecimalequals 方法等值比较?</think>### 为什么Java中不适合使用`BigDecimal.equals()`进行等值比较? 在Java中,`BigDecimal.equals()`方法存在一个关键设计缺陷:**它不仅比较数值大小,还会严格比较精度(scale)**。这会导致数值相同但精度不同的对象被判定为不相等,引发逻辑错误。以下是具体分析: --- ### 一、`equals()`方法的致命缺陷 #### 1. **精度敏感性问题 `BigDecimal`由两部分组成: - **非标度值(unscaledValue)**:存储实际数值(如 `1.00` → `100`) - **标度(scale)**:小数点后的位数(如 `1.00` → `scale=2`) `equals()`方法要求**数值和标度必须完全相同**才会返回`true`: ```java BigDecimal a = new BigDecimal("1.0"); // scale=1 BigDecimal b = new BigDecimal("1.00"); // scale=2 System.out.println(a.equals(b)); // false(数值相同但精度不同) ``` #### 2. **构造方式的影响 不同构造方式产生的精度差异会导致意外结果: ```java BigDecimal intVal = new BigDecimal(1); // scale=0 BigDecimal doubleVal = new BigDecimal(1.0); // scale=0(double构造) BigDecimal strVal = new BigDecimal("1.0"); // scale=1 System.out.println(intVal.equals(doubleVal)); // true(scale均为0) System.out.println(intVal.equals(strVal)); // false(scale不同) ``` > 💡 关键结论:**`equals()`不是纯数值比较**,它把 `1`、`1.0` 和 `1.00` 视为不同对象[^3]。 --- ### 二、正确方案:使用`compareTo()` #### ✅ 数值安全的比较方法 `compareTo()` **只比较数值大小**,忽略精度差异: ```java BigDecimal x = new BigDecimal("1.0"); BigDecimal y = new BigDecimal("1.00"); System.out.println(x.compareTo(y) == 0); // true(数值相等) ``` #### ✅ 统一精度后再比较 通过`stripTrailingZeros()`移除尾部零,使精度一致: ```java BigDecimal p = new BigDecimal("1.0"); BigDecimal q = new BigDecimal("1.00"); p = p.stripTrailingZeros(); // 移除尾部零 → scale=0 q = q.stripTrailingZeros(); // 移除尾部零 → scale=0 System.out.println(p.equals(q)); // true ``` --- ### 三、`equals()`引发问题的场景 #### 1. **数据库金额比较 ```java // 数据库返回的金额(精度可能不同) BigDecimal dbAmount1 = resultSet.getBigDecimal("amount"); // 值=1.0, scale=1 BigDecimal dbAmount2 = resultSet.getBigDecimal("amount"); // 值=1.0, scale=2 if (dbAmount1.equals(dbAmount2)) { // false!但数值相同 // 永远不会执行 } ``` #### 2. **计算结果的验证 ```java BigDecimal total = new BigDecimal("10.0") .divide(new BigDecimal("3"), 2, RoundingMode.HALF_UP); // 3.33 BigDecimal expected = new BigDecimal("3.33"); System.out.println(total.equals(expected)); // true(精度相同) // 若expected改为new BigDecimal("3.330") → false ``` --- ### 四、根本原因分析 | **比较方法** | 比较内容 | 数值相同精度不同时的结果 | |--------------------|------------------------|--------------------------| | **`equals()`** | 数值 + 精度 + 类型[^4] | ❌ 返回`false` | | **`compareTo()`** | 纯数值大小 | ✅ 返回`0`(相等) | > ⚠️ **设计陷阱**: > `BigDecimal`重写了`Object.equals()`,但将精度纳入相等性判断。这与直觉相悖(用户通常只关心数值是否相等)。 --- ### 五、最佳实践总结 1. **永远用`compareTo()==0`替代`equals()`** ```java // ✅ 正确方式 if (a.compareTo(b) == 0) { ... } ``` 2. **避免用`double`构造`BigDecimal`** ```java // ❌ 危险(精度不可控) new BigDecimal(0.1); // ✅ 安全(精确表示) new BigDecimal("0.1"); ``` 3. **必要时统一精度** ```java value = value.setScale(2, RoundingMode.HALF_UP); // 统一为2位小数 ``` --- ### 相关问题 1. 为什么用`double`构造`BigDecimal`会导致精度问题?[^4] 2. 如何确保`BigDecimal`在`HashMap`中作为键的正确性? 3. `BigDecimal.stripTrailingZeros()`的工作原理是什么? 4. 在分布式系统中传递`BigDecimal`时要注意什么? 5. `RoundingMode.HALF_UP`和`HALF_EVEN`在金融计算中的区别? [^1]: 阿里为什么不能使用 BigDecimalequals 方法等值比较? [^2]: 【Java开发日记】为什么不推荐使用 BigDecimalequals 方法等值比较? [^3]: 为什么不能使用 BigDecimalequals 方法等值比较 [^4]: Java开发手册中为什么禁止使用BigDecimalequals方法等值比较以及为什么禁止使用double直接构造 [^5]: 为什么不推荐使用 BigDecimalequals 方法等值比较
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

mangoBUPT

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值