The crucial distinction between equals and compareTo in Java
… or: Why 1 is not equals 1.0 when using BigDecimal.
Languages have sometimes their own special behavior, which you need to know when you want to use it right.
Sometimes you forget about it and then you remember some specific behavior when you try to use it.Let’s take a look at the crucial difference between equals and compareTo in Java.
equals of BigDecimal
Look at this two BigDecimals:
final BigDecimal BIG_DECIMAL_1 = BigDecimal.valueOf(1);
final BigDecimal BIG_DECIMAL_1_0 = BigDecimal.valueOf(1.0);
Normally you’d say that they have two equal values, and should be equal, because 1 = 1.0.
But when you do this Java will return false:
BIG_DECIMAL_1_0.equals(BIG_DECIMAL_1);
Side note: The same happens when you use BigDecimal.ONE for BIG_DECIMAL_1
This is due to the nature of the equals implementation. The Javadoc of BigDecimal#equals states:
Unlike
compareTo, this method considers twoBigDecimalobjects equal only they are equal in value and scale.
compareTo of BigDecimal
So, when elements have a natural ordering, then it’s better to use compareTo.
The following code will return 0, which means that the two BigDecimals are equal in their value (order):
BIG_DECIMAL_1_0.compareTo(BIG_DECIMAL_1);
Why to keep this in mind
This difference is pretty important when working with Comparable data. Not only should you prefer to use compareTo whenever you want to assure that the actual values are the same.
You also need to know which side-effects it may have and what to expect when passing objects around to other libraries or implementations.
HashSet vs. TreeSet
Let’s look at an example.
Create a HashSet and add BIG_DECIMAL_1 into it.
HashSet<BigDecimal> hashSet = new HashSet<>();
hashSet.add(BIG_DECIMAL_1);
Checking if the collection contains BIG_DECIMAL_1_0 will return false:
hashSet.contains(BIG_DECIMAL_1_0)
But when you also add BIG_DECIMAL_1_0 and check the size afterwards, you’ll see that the size is 2!
hashSet.add(BIG_DECIMAL_1_0);
hashSet.contains(BIG_DECIMAL_1_0); // = true
hashSet.size(); // = 2
This is due to the nature of the HashSet: It’s using equals and hashCode to check if an element already exists in the collection.
When you do the same with TreeSet you’ll get completely different results. As TreeSet is using compareTo to check if an element exists in the collection, it’ll not grow when adding BIG_DECIMAL_1_0. Furthermore it’ll return true when checking if the value exists after adding BIG_DECIMAL_1.
Here’s a code snippet to demonstrate this:
TreeSet<BigDecimal> treeSet = new TreeSet<>();
treeSet.add(BIG_DECIMAL_1);
treeSet.contains(BIG_DECIMAL_1_0); // = true
treeSet.add(BIG_DECIMAL_1_0);
treeSet.size(); // = 1
Conclusion
This post took a look into the effects of equals and compareTo and which difference they can make.
The takeoffs are:
equalsshould be used only for regular objects. WhereascompareToshould be used for objects that have a natural order.- It’s important to be aware of this, because it means, that
1 = 1.0doesn’t istrue. At least when usingBigDecimal. - It’s also important to know which methods other classes are using to compare objects. While
HashSetmakes use ofequalsandhashCode,TreeSetusescompareTo. This can lead to some surprising side-effects, like the one that aHashSetsuddenly has an object with the same value meaning inside itself.