Question

It says in the contract for the Comparator interface, that it must be consistent with equals.

Does this mean that Comparator = zero if equalsTo = true , or does it mean that Comparator = zero if and only if equalsTo = true?

I seem to remember that it is the second one, but I have come across lots of comparators which sort by non-unique sub properties.

For example, I might have objects which have a sub-property date, and I want to sort my list of objects by the date of submission. However, you can have several objects with the same date? What are the consequences of this? Surely there is a best practice solution to this problem already? How can I sort a collection by a property which is not guaranteed to be unique without violating the comparator contract? What are the consequences for this type of violation? Are they manageable?

Was it helpful?

Solution

It's not at all true that Comparator must be consistent with equals.

The docs merely warn for this situation:

Caution should be exercised when using a comparator capable of imposing an ordering inconsistent with equals to order a sorted set (or sorted map) (http://docs.oracle.com/javase/7/docs/api/java/util/Comparator.html)

If you have one ordering based on date, and another ordering based on date+time, you should simply implement multiple comparators.

Perhaps you are confusing Comparator with Comparable? For Comparable the docs strongly advice against this situation:

It is strongly recommended (though not required) that natural orderings be consistent with equals. (http://docs.oracle.com/javase/7/docs/api/java/lang/Comparable.html)

This difference makes sense if you realize that an object can only have 1 implementation of Comparable, but multiple of Comparator. The whole idea of Comparator is to have multiple ways of comparing the same class.

OTHER TIPS

edit you could have mulitple Comparators and as popovitsj stated they don't necessarily have to be consistent with equals

(although I presume most of the time you have Comparator.compare(obj1, obj2) == 0 <=> obj1.equals(obj2) == true)

If you want to have specific sort results when sorting by non-unique field, you need to customize your Comparator to account for these,

for example, while implementing compare() you encounter that obj1.date == obj2.date, then you should compare other important fields (name, age, etc) to rank obj1 vs obj2 accordingly and return corresponding value.

Hope that helps.

As you suspect, in order for compareTo() to be consistent with equals(), compareTo() must always return 0 when equals() returns true. Similarly, if equals returns false, then compareTo must not return 0.

However, as popovitsj has pointed out in his answer, consistency with equals() is not a requirement. As such, the above only applies when you are attempting to make the two methods consistent.

It says in the contract for the Comparator interface, that it must be consistent with equals.

That's not entirely correct; see @popovitjs' answer.

Does this mean that Comparator = zero if equalsTo = true , or does it mean that Comparator = zero if and only if equalsTo = true?

It means the latter. However, it is not actually a hard requirement for Comparator objects.

I seem to remember that it is the second one, but I have come across lots of comparators which sort by non-unique sub properties.

Well that's reasonable, given that it is not actually a hard requirement. In fact, a Comparator that is inconsistent with equals(Object) is just fine if you are going to use it with Arrays.sort(...). The problems only arise with TreeSet and TreeMap.

For example, suppose that you have a Comparator<E> C that says e1 and e2 are not equal, but e1.equals(e2) returns true. Now suppose that you create a TreeSet<E> instance using the comparator, and then add e1 and e2 to that set. The set's tree is organized based on the comparator, and therefore e1 and e2 will slot into different places in the search tree, and will both be elements of the set. But that violates the primary invariant of a Set ... which is based on the equals method.

As the javadoc for TreeSet says:

"Note that the ordering maintained by a set (whether or not an explicit comparator is provided) must be consistent with equals if it is to correctly implement the Set interface. (See Comparable or Comparator for a precise definition of consistent with equals.) This is so because the Set interface is defined in terms of the equals operation, but a TreeSet instance performs all element comparisons using its compareTo (or compare) method, so two elements that are deemed equal by this method are, from the standpoint of the set, equal. The behavior of a set is well-defined even if its ordering is inconsistent with equals; it just fails to obey the general contract of the Set interface."

And this answers the last part of your question.

What are the consequences for this type of violation?

If you use an inconsistent Comparator in a TreeSet or TreeMap, the collection will not obey the Set or Map contract.

a and b may not be equal. But if comparator is zero when comparing a and b it should be zero when comparing b and a.

In this answer say:

Typically, if 2 objects are equal from an equals perspective but not from a compareTo perspective, you can store both objects as keys in a TreeMap. This can lead to un-intuitive behaviour. It can also be done on purpose in specific situations.

But this is for specific situations in general nothing stops you from having an inconsistant behaviour where equals and compareTo dont behave consistently.

One example, this morning someone asked: Move specific items to the end of a list

Most of the answers have comparators that returns 0 for elements that are not equal.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top