Java equals() vs. compareTo() contract

1 05 2016

The definitions

Object’s equals(Object obj) method:

Indicates whether some other object is "equal to" this one

compareTo(T t) is an abstract method from Comparable interface and it:

Compares this object with the specified object for order. 
Returns a negative integer, zero, or a positive integer 
as this object is less than, equal to, or greater than the specified object.

The contract

That being said here seems to appear the contract between those two methods: compareTo should return 0 if equals returns true. This is yet another contract that the equals() is involved (mind the hashCode()). In fact the Comparable JavaDoc says it clearly:

It is strongly recommended, but not strictly required that 
(x.compareTo(y)==0) == (x.equals(y)).

Since it is not strictly required, the JavaDoc clears out things and says:

Generally speaking, any class that implements the Comparable interface 
and violates this condition should clearly indicate this fact. 
The recommended language is: 
"Note: this class has a natural ordering that is inconsistent with equals."

Natural ordering

A class should implement Comparable interface if it is eligible to natural ordering. Natural ordering is perceived as an order where an object can be compared to another of the same type (alphabetical comparison for String, numerical for Number or any other specific to your domain).

If your class does not have a natural ordering that is consistent with equals() but you still need to sort it, you should consider using the Comparator interface instead of Comparable.

In fact the String class follows the equals() and compareTo() contract and it sorts Strings alphabetically according to ASCII characters ordering. Note that this ordering sorts lowercase letters in front of upper case.

The BigDecimal breaks the contract

The BigDecimal class violates the recommendation and its equals() is not consistent with compareTo() inĀ  a matter of a scale. The documentation clearly states it:

according to equals(): 2.0 is not equal to 2.00
according to compareTo(): 2.0 is equal to 2.00

Why? Who knows. There are discussions that in physics for example the 2.0 definetely should not be considered as equal to 2.00 due to the precision. However in most of the domains the values can be considered as equal.

Problems when violating the contract

The problem that arises is for example putting the BigDecimal in a HashSet:

Set<BigDecimal> set = new HashSet<>();
set.add(new BigDecimal("0.20"));
if (set.contains(new BigDecimal("0.2")) { // Returns false, but should return true

Because of that the HashSet is usable with BigDecimal if you define the scale that its elements have. So if you put the number with two digits precision, you should always look for two digits precision as well.

The other solution is to use TreeSet, since it uses the compareTo() to search for elements (ignoring the scale). However the TreeSet is sorted as opposed to the HashSet. If you do not need sorting then you ahve to consider whether it hurts your performance (putting an element to the sorted set is more expensive – due to the sorting).


%d bloggers like this: