为什么BigDecimal自然排序与equals不一致?

Jee*_*ets 25 java bigdecimal

来自JavadocBigDecimal:

注:如果应谨慎BigDecimal对象作为一个键SortedMap或元素的SortedSetBigDecimal自然顺序与等号一致.

例如,如果你创建HashSet并添加new BigDecimal("1.0")new BigDecimal("1.00")它的集将包含两个元素(因为该值有不同的尺度,所以是不相等的根据equalshashCode),但如果你有做同样的事情TreeSet,该Set只包含一个元素,因为使用时值比较相等compareTo.

这种不一致背后有什么具体原因吗?

alf*_*sin 8

从BigDecimal 的OpenJDK实现:

/**
     * 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.inflate().equals(xDec.inflate());
    }
Run Code Online (Sandbox Code Playgroud)

更多来自实施:

 * <p>Since the same numerical value can have different
 * representations (with different scales), the rules of arithmetic
 * and rounding must specify both the numerical result and the scale
 * used in the result's representation.
Run Code Online (Sandbox Code Playgroud)

这就是为什么执行equals需要scale考虑.将字符串作为参数的构造函数实现如下:

    public BigDecimal(String val) {
        this(val.toCharArray(), 0, val.length());
    }
Run Code Online (Sandbox Code Playgroud)

其中第三参数将被用于scale(在另一个构造函数)这就是为什么串1.01.00将创建不同BigDecimals的(具有不同的尺度).

来自Effective Java作者:Joshua Bloch:

compareTo契约的最后一段是一个强烈的建议,而不是一个真正的规定,只是说明compareTo方法所施加的相等测试通常应该返回与equals方法相同的结果.如果遵守此规定,则compareTo方法强加的顺序与equals一致.如果它被违反,则说明顺序与equals不一致.compareTo方法强加与equals不一致的顺序的类仍然有效,但包含该类元素的有序集合可能不遵守相应集合接口(Collection,Set或Map)的常规协定.这是因为这些接口的一般契约是用equals方法定义的,但是已排序的集合使用compareTo强加的相等性测试来代替equals.如果发生这种情况,这不是灾难,但需要注意的是.

  • @Val感谢你的链接 - 你有一个好点,即使它有点粗鲁的措辞. (5认同)
  • -1因为这不能回答这个问题,并且`.equals()`不会在任何想象中"检查它是否是同一个对象".实际上,`.equals()`存在*因为*你要检查两个*不同的*对象是否彼此相等.否则你可以使用`==`. (4认同)
  • @LuiggiMendoza在任何JVM实现上加载了相同的`BigDecimal`字节代码.JVM只是驱动乘客(Java库)的工具,因此它与我们的问题无关. (3认同)
  • @LuiggiMendoza JVM与BigDecimal实现(Java库)无关.你混淆了两件不同的事情. (2认同)