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

从Javadoc for BigDecimal

注意:如果BigDecimal对象用作SortedMap键或SortedSet元素,则应该小心,因为BigDecimal自然顺序 与equals不一致

例如,如果您创建一个HashSet并向其添加new BigDecimal("1.0")new BigDecimal("1.00") ,该集合将包含两个元素(因为这些值具有不同的比例,因此根据equalsequalshashCode ),但是如果你使用TreeSet做同样的事情,那么该集合将只包含一个元素,因为当你使用compareTo时,这些值相等。

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

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

更多来自实施:

  * 

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.

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

  public BigDecimal(String val) { this(val.toCharArray(), 0, val.length()); } 

其中第三个参数将用于scale (在另一个构造函数中),这就是为什么字符串1.01.00将创建不同的BigDecimals(具有不同的比例)。

来自Effective Java作者:Joshua Bloch:

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

在算术精度的上下文中,行为似乎是合理的,其中尾随零是有效数字 ,1.0不具有与1.00相同的含义。 使它们不平等似乎是一个合理的选择。

然而,从比较的角度来看,两者都不大于或小于另一个,并且Comparable接口需要总顺序(即每个BigDecimal必须与任何其他BigDecimal相当)。 这里唯一合理的选择是定义一个总订单,这样compareTo方法会认为两个数字相等。

请注意,只要记录,等于和compareTo之间的不一致就不是问题。 甚至有时甚至是人们所需要的 。

BigDecimal通过两个数字,一个整数和一个比例来工作。 整数是“数字”,标度是小数点右边的位数。 基本上是10个浮点数。

当您说"1.0""1.00"这些在BigDecimal表示法中是技术上不同的值:

 1.0 integer: 10 scale: 1 precision: 2 = 10 x 10 ^ -1 1.00 integer: 100 scale: 2 precision: 3 = 100 x 10 ^ -2 

在科学记数法中你不会做任何一个,它应该是1 x 10 ^ 0或只是1 ,但BigDecimal允许它。

在比较中,忽略比例并将它们评估为普通数字, 1 == 1 。 在equals整数和比例值进行比较时, 10 != 1001 != 2 。 BigDecimal equals方法忽略了object == this我假设object == this检查,因为意图是每个BigDecimal都被视为一种数字,而不是一个对象。

我想把它比作这个:

 // same number, different types float floatOne = 1.0f; double doubleOne = 1.0; // true: 1 == 1 System.out.println( (double)floatOne == doubleOne ); // also compare a float to a double Float boxFloat = floatOne; Double boxDouble = doubleOne; // false: one is 32-bit and the other is 64-bit System.out.println( boxInt.equals(boxDouble) ); // BigDecimal should behave essentially the same way BigDecimal bdOne1 = new BigDecimal("1.0"); BigDecimal bdOne2 = new BigDecimal("1.00"); // true: 1 == 1 System.out.println( bdOne1.compareTo(bdOne2) ); // false: 10 != 100 and 1 != 2 ensuring 2 digits != 3 digits System.out.println( bdOne1.equals(bdOne2) ); 

因为BigDecimal允许特定的“精度”,所以比较整数和比例与比较数字和精度大致相同。

虽然在谈论BigDecimal的precision()方法时有一点需要注意,如果BigDecimal为0,它总是返回1.在这种情况下,compareTo && precision计算true,equals计算false。 但是0 * 10 ^ -1不应该等于0 * 10 ^ -2因为前者是2位数0.0 ,后者是3位数0.00 。 equals方法是比较值和数字。

我想BigDecimal允许尾随零是奇怪的,但这基本上是必要的。 执行像"1.1" + "1.01"这样的数学运算需要转换,但"1.10" + "1.01"不需要。

因此,比较BigDecimals作为数字, equals将BigDecimals作为BigDecimals进行比较。

如果比较是不需要的,请使用无关紧要的List或数组。 HashSet和TreeSet当然是专门为保存独特元素而设计的。

答案非常简短。 equals()方法比较对象,而compareTo()比较值。 在BigDecimal的情况下,不同的对象可以表示相同的值。 这就是为什么equals()可能返回false,而compareTo()返回0。

等于对象=>相等的值

等值= />等于对象

对象只是一些现实世界价值的计算机表示。 例如,相同的图片可能以GIF和JPEG格式表示。 这非常像BigDecimal,其中相同的值可能具有不同的表示。