From the JavaDoc of TreeMap :
Note that the ordering maintained by a sorted map (whether or not an explicit comparator is provided) must be consistent with equals if this sorted map is to correctly implement the Map interface. (See Comparable or Comparator for a precise definition of consistent with equals.) This is so because the Map interface is defined in terms of the equals operation, but a map performs all key comparisons using its compareTo (or compare) method, so two keys that are deemed equal by this method are, from the standpoint of the sorted map, equal. The behavior of a sorted map is well-defined even if its ordering is inconsistent with equals; it just fails to obey the general contract of the Map interface.
Can some one give an concrete example to demonsrate the problem that might occur if ordering is not consistent with equals ? Take for example User defined class that has a natural ordering i.e it implements Comparable . Also do all internal classes in JDK maintain this invariant?
Here's a simple but realistic example of what can happen if a comparison method is inconsistent with equals. In the JDK,
BigDecimal
implementsComparable
but its comparison method is inconsistent with equals. For example:This is because the comparison method of
BigDecimal
considers only the numeric value, butequals
also considers the precision. Since0.0
and0.00
have different precisions, they are unequal even though they have the same numeric value.Here's an example of what it means for a
TreeSet
to violate the general contract ofSet
. (It's the same situation withTreeMap
andMap
but it's a bit easier to demonstrate using sets.) Let's compare the results ofcontains
to the result of getting the element out of the set and callingequals
:The surprising thing here is that the
TreeSet
says it contains the objectzz
, but it's unequal to the element that's actually contained in the set. The reason is thatTreeSet
uses its comparison method (BigDecimal.compareTo
) to determine set membership, notequals
.Now let's compare
TreeSet
toHashSet
:This is strange. We have two sets that are equal, but one set says that it contains an object but another set says that it doesn't contain the same object. Again, this reflects the fact that
TreeSet
is using the comparison method whereasHashSet
is usingequals
.Now let's add the other object to a
HashSet
and see what happens:Now that's weird. One set says it's equal to the other, but the other set says it's not equal to the first! To understand this, you need to understand how equality of sets is determined. Two sets are considered equal if a) they are of the same size, and b) each element in the other set is also contained in this set. That is, if you have
then the equality algorithm looks at the sizes and then it iterates over set2, and for each element it checks whether that element is contained in set1. That's where the asymmetry comes in. When we do
both sets are of size 1, so we proceed to the iteration step. We iterate over
hs2
and use then call theTreeSet.contains
method -- which uses the comparison method. As far as theTreeSet
is concerned, it's equal to theHashSet
hs2.Now when we do
the comparison goes the other way. We iterate over the
TreeSet
and get its element, and askhs2
whether itcontains
that element. Since theHashSet.contains
uses equals, it returns false, and the overall result is false.The contract of the Comparable interface allows for non-consistent behaviour:
So in theory, it is possible that a class in the JDK had a
compareTo
not consistent withequals
. One good example is BigDecimal.Below is a contrived example of a comparator that is not consistent with equals (it basically says that all strings are equal).
Output:
Say we have this simple
Student
class implementingComparable<Student>
but not overridingequals()
/hashCode()
. Of courseequals()
is not consistent withcompareTo()
- two different students with the sameage
aren't equal:We can safely use it in
TreeMap<Student, String>
:The results are easy to predict: students are nicely sorted according to their age (despite being inserted in different order) and fetching student using
new Student(22)
key works as well and returns"twenty two"
. This means we can safely useStudent
class inTreeMap
.However change
students
toHashMap
and things go bad:Obviously the enumeration of items returns "random" order due to hashing - that's fine, it doesn't violate any
Map
contract. But the last statement is completely broken. BecauseHashMap
usesequals()
/hashCode()
to compare instances, fetching value bynew Student(22)
key fails and returnsnull
!This is what the JavaDoc tries to explain: such classes will work with
TreeMap
but might fail to work with otherMap
implementations. Note thatMap
operations are documented and defined in terms ofequals()
/hashCode()
, e.g.containsKey()
:Thus I don't believe there are any standard JDK classes that implemente
Comparable
but fail to implementequals()
/hashCode()
pair.Here is another example of when consistency with equals AND total ordering are important to implement.
Say we have an object
MyObject
which has two fields:id
andquantity
.id
as its name suggests is the natural key of the object andquantity
is just an attribute.Let's imagine that we want to use a collections of
MyObject
sorted byquantity
descending. The First comparator we can write is:Using
MyObject
instances equipped with this comparator in a TreeMap/TreeSet fails because it comparator is not consistent with equals (see full code below). Let's make it consistent with equals:However, this fails again to fit in TreeSet/TreeMap! (see full code below) This is because the ordering relation is not total, i.e. not any two objects can be strictly put in an ordering relationship. In this comparator, when
quantity
fields are equal, the resulting ordering is undetermined.A better comparator would be:
This comparator ensures that:
equal
(initial check for equality)id
as a discriminant ordering field whenquantity
are equalFull Testing Code:
Output:
Conclusion:
Although I think it is very legitimate to segregate identity from ordering conceptually. For instance, in relational database terms:
works perfectly. We don't care about object identity here, nor we want total ordering
However, due to constraints in tree based collections implementation, one has to ensure that any comparator they write: