为什么BigDecimal自然排序与equals不一致?
从Javadoc for BigDecimal
:
注意:如果
BigDecimal
对象用作SortedMap
键或SortedSet
元素,则应该小心,因为BigDecimal
的自然顺序 与equals不一致 。
例如,如果您创建一个HashSet
并向其添加new BigDecimal("1.0")
和new BigDecimal("1.00")
,该集合将包含两个元素(因为这些值具有不同的比例,因此根据equals
不equals
和hashCode
),但是如果你使用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.0
和1.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 != 100
和1 != 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,其中相同的值可能具有不同的表示。