Java开发手册学习(2)为什么禁止使用 BigDecimal 的 equals 方法做等值比较?

308 阅读4分钟

3a6efe38dfe84aac8f16f7d750df45e8_0.png

阿里巴巴的Java开发手册中,禁止使用BigDecimal的equals方法做等值比较的,原因是因为BigDecimal的equals方法在比较时会考虑精度,而不仅仅是数值的相等性。这可能会导致一些意外的结果,特别是在涉及到货币计算等需要精确计算的场景中。

文档说明

【强制】如上所示BigDecimal的等值比较应使用compareTo()方法,而不是equals()方法。 说明:equals()方法会比较值和精度(1.0与1.00返回结果为false),而compareTo()则会忽略精度。

案例分析

下面我将通过一个代码案例来说明这个问题。

假设我们有两个BigDecimal对象,分别表示0.1和0.10这两个数值:

BigDecimal num1 = new BigDecimal("0.1");
BigDecimal num2 = new BigDecimal("0.10");
System.out.println(num1.equals(num2));
System.out.println(num1.compareTo(num2));

返回结果

false
0

BigDecimal equals返回结果说明

如果我们使用equals方法进行等值比较,根据BigDecimal的equals方法的实现,它会考虑数值的精度。因此,num1.equals(num2)的结果将会是false,即使这两个数值在数值上是相等的。

这是因为BigDecimal的equals方法会比较数值的精度,即小数点后的位数。在上面的例子中,num1和num2的精度分别是1和2,因此它们被认为是不相等的。

BigDecimal compareTo返回结果说明

  • 如果返回值为0,表示两个BigDecimal对象的数值相等。
  • 如果返回值小于0,表示调用compareTo方法的对象小于被比较的对象。
  • 如果返回值大于0,表示调用compareTo方法的对象大于被比较的对象。

compareTo方法只比较数值的大小,而不考虑精度。因此,即使num1num2的精度不同,它们的数值相等时,compareTo方法仍然会返回0,

在上面的代码中,num1.compareTo(num2)返回的结果是0,这意味着num1num2的数值是相等的。

BigDecimal equals源码分析

    /**
     * Compares this {@code BigDecimal} with the specified
     * {@code Object} for equality.  Unlike {@link
     * #compareTo(BigDecimal) compareTo}, this method considers two
     * {@code 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).
     *
     * @param  x {@code Object} to which this {@code BigDecimal} is
     *         to be compared.
     * @return {@code true} if and only if the specified {@code Object} is a
     *         {@code BigDecimal} whose value and scale are equal to this
     *         {@code BigDecimal}'s.
     * @see    #compareTo(java.math.BigDecimal)
     * @see    #hashCode
     */
    @Override
    public boolean equals(Object x) {
        if (!(x instanceof BigDecimal))
            return false;
        BigDecimal xDec = (BigDecimal) x;
        if (x == this)
            return true;
        if (scale != xDec.scale)
            return false;
        long s = this.intCompact;
        long xs = xDec.intCompact;
        if (s != INFLATED) {
            if (xs == INFLATED)
                xs = compactValFor(xDec.intVal);
            return xs == s;
        } else if (xs != INFLATED)
            return xs == compactValFor(this.intVal);
​
        return this.inflated().equals(xDec.inflated());
    }

根据BigDecimal equals源码注释说明可以看出

BigDecimal与指定的Object进行相等性比较。与compareTo不同,此方法仅当两个BigDecimal对象的值和小数位数相等时才认为它们相等(因此,使用此方法进行比较时,2.0不等于2.00)。

源码中 if (scale != xDec.scale) 就是对精度的比较

BigDecimal compareTo源码分析

    /**
     * Compares this {@code BigDecimal} with the specified
     * {@code BigDecimal}.  Two {@code BigDecimal} objects that are
     * equal in value but have a different scale (like 2.0 and 2.00)
     * are considered equal by this method.  This method is provided
     * in preference to individual methods for each of the six boolean
     * comparison operators ({@literal <}, ==,
     * {@literal >}, {@literal >=}, !=, {@literal <=}).  The
     * suggested idiom for performing these comparisons is:
     * {@code (x.compareTo(y)} &lt;<i>op</i>&gt; {@code 0)}, where
     * &lt;<i>op</i>&gt; is one of the six comparison operators.
     *
     * @param  val {@code BigDecimal} to which this {@code BigDecimal} is
     *         to be compared.
     * @return -1, 0, or 1 as this {@code BigDecimal} is numerically
     *          less than, equal to, or greater than {@code val}.
     */
    public int compareTo(BigDecimal val) {
        // Quick path for equal scale and non-inflated case.
        if (scale == val.scale) {
            long xs = intCompact;
            long ys = val.intCompact;
            if (xs != INFLATED && ys != INFLATED)
                return xs != ys ? ((xs > ys) ? 1 : -1) : 0;
        }
        int xsign = this.signum();
        int ysign = val.signum();
        if (xsign != ysign)
            return (xsign > ysign) ? 1 : -1;
        if (xsign == 0)
            return 0;
        int cmp = compareMagnitude(val);
        return (xsign > 0) ? cmp : -cmp;
    }

根据BigDecimal compareTo源码注释说明可以看出

此BigDecimal与指定的BigDecimal进行比较。该方法将具有相同值但具有不同标度(例如2.0和2.00)的两个BigDecimal对象视为相等。

最后总结

通过使用compareTo方法,我们可以得到正确的等值比较结果,而不会受到BigDecimal的精度考虑的影响。

结合以上分析来看,阿里巴巴的Java开发手册,禁止使用BigDecimal的equals方法做等值比较。是为了避免在精确计算的场景中出现意外的结果。通过使用compareTo方法进行等值比较,可以得到正确的比较结果。这样可以保证代码的准确性和可靠性,特别是在涉及到货币计算等需要精确计算的场景中。