起因

先看看几行代码:

public class BigDecimalTest {
    public static void main(String[] args) {
        BigDecimal test = new BigDecimal("0.0");
        System.out.println("equals   方法判断是否为0:" +test.equals(BigDecimal.ZERO));
        System.out.println("compareTo方法判断是否为0:" + (test.compareTo(BigDecimal.ZERO) == 0));
    }
}

其运行结果让我很是诧异,本来我以为两个都是true,但是结果并不是这样:

"C:\Program Files\Java\jdk1.8.0_171\bin\java.exe""......
equals   方法判断是否为0:false
compareTo方法判断是否为0:true

Process finished with exit code 0

在我看来equals方法和compareTo方法都是用来比较对象,那么针对相同对象进行相等性判断应该是一样的。但是结果为什么会出现这种差异性呢?

源码

先看一下关键代码,equals和compareTo两个方法的实现和注释。

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());
    }

注释描述翻译:
该方法用于与指定对象比较相等性,与CompareTo不同,这个方法认为两个BigDecimal对象的数值(value)和标度(scale)均相等的情况才相等(该方法比较2.0和2.00时结果是不相等的)。

简单看一下理一下判断流程:
1. 判断目标对象类型,是否是BigDecimal,不是则直接返回false
2. 直接使用==比较两个对象的hashCode,如果相等直接返回true
3. 比较两个对象标度(scale),不相等则返回false
4. 分别获取两个对象的intCompact值,并判断其合法性,如有需要会调用compactValFor方法获取intVal中的值,然后进行比较。如果使用compactValFor方法也无法获取到有效数字,进行下一步。
5. 这也是最终比较,一般来说是走不到这一步的。直接进行intVal比较,intVal的类型为BigInteger,其比较结果为BigIntegerequals方法的比较结果。

关于流程中的几个变量和方法说明后面会有相应说明

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对象。该方法中在判断对象相等时只需要考虑数值上的相等性而不需要考虑其标度。这一点是equals是有很大的区别。

简单看一下理一下判断流程:
1. 当两个对象的标度相等,且intCompact均有效的情况下,直接比较intCompact的相等性。比较流程是连续使用三目运算符先判断是否相等,然后判断大小。
2. 以上条件不满足的情况下,通过获取数值的符号(正负)进行判断。如果两个数字异号,则直接比对大小;如果同号且为0,直接返回相等。
3. 如果两个对象同号,且不等于0。则先比较两个数绝对值的大小,然根据其正负号进行取反。

相关变量和方法

    /**
     * The unscaled value of this BigDecimal, as returned by {@link
     * #unscaledValue}.
     *
     * @serial
     * @see #unscaledValue
     */
    private final BigInteger intVal;
    
    /**
     * Sentinel value for {@link #intCompact} indicating the
     * significand information is only available from {@code intVal}.
     */
    static final long INFLATED = Long.MIN_VALUE;
    
    /**
     * If the absolute value of the significand of this BigDecimal is
     * less than or equal to {@code Long.MAX_VALUE}, the value can be
     * compactly stored in this field and used in computations.
     */
    private final transient long intCompact;
    /**
     * Returns the compact value for given {@code BigInteger}, or
     * INFLATED if too big. Relies on internal representation of
     * {@code BigInteger}.
     */
    private static long compactValFor(BigInteger b) {
        int[] m = b.mag;
        int len = m.length;
        if (len == 0)
            return 0;
        int d = m[0];
        if (len > 2 || (len == 2 && d < 0))
            return INFLATED;

        long u = (len == 2)?
            (((long) m[1] & LONG_MASK) + (((long)d) << 32)) :
            (((long)d)   & LONG_MASK);
        return (b.signum < 0)? -u : u;
    }
    /**
     * Returns appropriate BigInteger from intVal field if intVal is
     * null, i.e. the compact representation is in use.
     */
    private BigInteger inflated() {
        if (intVal == null) {
            return BigInteger.valueOf(intCompact);
        }
        return intVal;
    }

注释描述翻译:
1. intVal变量:不包含标度的有效数字整数值。
2. intCompact变量:BigDecimal的有效数字的绝对值,如果该值小于等于Long.MAX_VALUE,则存储在在这个字段中,并用于计算。
3. INFLATED常量:intCompact的标准值,一般是当前对象的数值过大,intCompact无法存储该数据的时候,就会将intCompact数值为该值。
4. compactValFor方法:用于返回intVal中的数字,如果数字过大则会返回INFLATED
5. inflated方法:用于获取intVal变量。

根据上面的描述可以看出intCompact在BigDecimal对象中的重要性,BigDecimal中有两个变量特别重要:intCompactscala。这两个值可以就是科学记数法(a×10^n)中的的基数a (intcompact)和标度n(scala)。

思考:
- 从变量声明中可以看到intCompact为常量,同时使用transient关键字标记。为什么这个变量需要transient标记呢?

结论

其实通过两个方法的注释就能看出,通过两个方法进行相等性判断产生不同结果的根本原因是对标度的判断。至于为什么这么做,其原因个人认为主要是内部采用了科学记数法的缘故吧。科学技术中基数相等,标度不一样的情况,也是被认为是不相等的。

——————————————————————————
行路不知花开处,蓦然回首芷兰香。