5

If I implement a custom comparator is it considered good practice to overide equals besides compare?
Additionally is there a defined contract for a Comparator?

Jim
  • 18,826
  • 34
  • 135
  • 254

5 Answers5

4

The javadoc for Comparator states:

The ordering imposed by a comparator c on a set of elements S is said to be consistent with equals if and only if c.compare(e1, e2)==0 has the same boolean value as e1.equals(e2) for every e1 and e2 in S.

Caution should be exercised when using a comparator capable of imposing an ordering inconsistent with equals to order a sorted set (or sorted map). Suppose a sorted set (or sorted map) with an explicit comparator c is used with elements (or keys) drawn from a set S. If the ordering imposed by c on S is inconsistent with equals, the sorted set (or sorted map) will behave "strangely." In particular the sorted set (or sorted map) will violate the general contract for set (or map), which is defined in terms of equals.

So the answer would be yes, it is good practice (for your first question at least).

Community
  • 1
  • 1
maba
  • 47,113
  • 10
  • 108
  • 118
  • What you quote does not say that it is good practice. It only states when equals and compareTo are consistent. – assylias Oct 04 '12 at 09:22
  • @assylias True, I added the second paragraph from the javadoc to make it clear. – maba Oct 04 '12 at 09:24
4

The contract of Comparator is defined in its javadoc. In particular:

Caution should be exercised when using a comparator capable of imposing an ordering inconsistent with equals to order a sorted set (or sorted map). Suppose a sorted set (or sorted map) with an explicit comparator c is used with elements (or keys) drawn from a set S. If the ordering imposed by c on S is inconsistent with equals, the sorted set (or sorted map) will behave "strangely." In particular the sorted set (or sorted map) will violate the general contract for set (or map), which is defined in terms of equals.

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.

For example, this answer shows an example where it is desirable that equals and compareTo be inconsistent.

Community
  • 1
  • 1
assylias
  • 321,522
  • 82
  • 660
  • 783
3

I think you only have to override equals if you feel you are testing equality. Often when I write comparators they are comparing properties within an object and I continue to use the compareTo on these properties.

For example

public class ToCompare {

    public static final Comparator<ToCompare> ID_COMPARATOR = new Comparator(){
        @Override
        public int compare(ToCompare o1, ToCompare o2) { 
             return o1.id.compareTo(o2.id);
        }
    }
    private Integer id;
    //... other fields here

}

I tend to store these comparators in the object as public static final fields to they are accessible from any code that may want to sort this object. If the object is updated (fields added/removed) then I can see straight away if there are problems with the comparator rather then going through all my code finding problems.

RNJ
  • 15,272
  • 18
  • 86
  • 131
1

Yes. as suggested in java doc of Comparator I would say it will depend on your needs.

For example, suppose one adds two elements a and b such that (a.equals(b) && c.compare(a, b) != 0) to an empty TreeSet with comparator c. The second add operation will return true (and the size of the tree set will increase) because a and b are not equivalent from the tree set's perspective, even though this is contrary to the specification of the Set.add method.

But if you consider below example where you only have to sort particular list then you might don't have to.

Lets take an example of Class Employee which has two properties id, name.

class Employee {
    int id;// sample so access not considered
    String name;// sample so access not considered
}

Suppose there are two separate options to sort list on id or name separately. In order to achieve this I would need to implement two comparators one which compares on id and other which compares on name.

Now if I want to implement comparator based on id as its type is int so wrapper type is Integer and as Integer provides compareTomethod just use it.

Same is the case of String. This way you will have to hardly worry about writing compare or compareTo method.

I hardly need to think about while writing compare method since I always use built in methods on properties of objects.

class EmpIdComparator implements Comparator<Employee> {

    @Override
    public int compare(Employee o1, Employee o2) {
        return Integer.valueOf(o1.id).compareTo(o2.id);// Make use of
                                                        // existing
                                                        // comparators
                                                        // provided by java
    }

}

class EmpNameComparator implements Comparator<Employee> {

    @Override
    public int compare(Employee o1, Employee o2) {
        return o1.name == null ? o2.name == null ? 0 : 1 : o1.name
                .compareTo(o2.name);// Make use of
        // existing
        // comparators
        // provided by java
    }

}
Amit Deshpande
  • 19,001
  • 4
  • 46
  • 72
  • Should I take care of `null`?Isn't the contract to throw `NPE` for `null` arguments? – Jim Oct 04 '12 at 11:31
  • @Jim If comparator does not permit null then it should throw NPE other wise not. – Amit Deshpande Oct 04 '12 at 11:35
  • So there is no global contract for this? – Jim Oct 04 '12 at 11:38
  • Also you don't check for `o1` or `o2` if is `null`.Why? – Jim Oct 04 '12 at 11:39
  • @Jim Answer to your first question :Java doc it self documents behaviour that way. For second question I have assumed that collection will not contain `null.` Should sort happen on collections containing null value is debatable. It is your choice. But check for properties is necessary since object might not be null be properties might be null. – Amit Deshpande Oct 04 '12 at 11:44
0

I feel that the other answers miss the point: Comparator's compare-method compares the two given parameters, while a Comparator's equals-method compares the Comparator itself with another Object.

The javadoc for Comparator#equals of at least newer JDKs explains it in detail:

/**
 * Indicates whether some other object is &quot;equal to&quot; this
 * comparator.  This method must obey the general contract of
 * {@link Object#equals(Object)}.  Additionally, this method can return
 * <tt>true</tt> <i>only</i> if the specified object is also a comparator
 * and it imposes the same ordering as this comparator.  Thus,
 * <code>comp1.equals(comp2)</code> implies that <tt>sgn(comp1.compare(o1,
 * o2))==sgn(comp2.compare(o1, o2))</tt> for every object reference
 * <tt>o1</tt> and <tt>o2</tt>.<p>
 *
 * Note that it is <i>always</i> safe <i>not</i> to override
 * <tt>Object.equals(Object)</tt>.  However, overriding this method may,
 * in some cases, improve performance by allowing programs to determine
 * that two distinct comparators impose the same order.
 *
 * @param   obj   the reference object with which to compare.
 * @return  <code>true</code> only if the specified object is also
 *          a comparator and it imposes the same ordering as this
 *          comparator.
 * @see Object#equals(Object)
 * @see Object#hashCode()
 */
boolean equals(Object obj);

As such it is seldom useful to override the equals-method of Comparator. Comparable would be different, though.

Markus Kull
  • 1,471
  • 13
  • 16