The crucial distinction between equals and compareTo in Java

Posted on Sep 21, 2023

… 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 two BigDecimal objects 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:

  • equals should be used only for regular objects. Whereas compareTo should be used for objects that have a natural order.
  • It’s important to be aware of this, because it means, that 1 = 1.0 doesn’t is true. At least when using BigDecimal.
  • It’s also important to know which methods other classes are using to compare objects. While HashSet makes use of equals and hashCode, TreeSet uses compareTo. This can lead to some surprising side-effects, like the one that a HashSet suddenly has an object with the same value meaning inside itself.

Want to know more?

Keep on reading and choose one of the related articles. You can also check the home page for my latest thoughts, notes and articles.