6

Can somebody put some light on what are the Consequences when compareTo() is inconsistent with equals() of a class. I have read that if Obj1.compareTo(Obj2) = 0 then it's not mandatory to be Obj1.equals(Obj2) = true. But what is the consequence if this happens. Thanks.

robasta
  • 4,621
  • 5
  • 35
  • 53
Trying
  • 14,004
  • 9
  • 70
  • 110
  • @Sam I am can you be please more elaborative. – Trying Mar 22 '13 at 14:31
  • 1
    The question is very vague. The consequence is "if you compare them in two different ways, you get two different answers" - the *follow-on* consequence depends on why you're performing that comparison. – Jon Skeet Mar 22 '13 at 14:32
  • for instance, i fyou typed a line like `if (Obj1.equals(Obj2) && Obj1.compareTo(Obj2)!= 0){throw new exception();}` then them being inconsistant would throw exceptions – Sam I am says Reinstate Monica Mar 22 '13 at 14:33
  • These issues i ran into a lot when had to implement a comparator. Equals did one thing but comparator absolutely another (i used it for sorting). One has to be carefull not to loose elements when implementing comparable. – Emil Iakoupov Mar 22 '13 at 14:38
  • possible duplicate of [Comparator best practice](http://stackoverflow.com/questions/12724051/comparator-best-practice) – assylias Mar 22 '13 at 14:43
  • 1
    [BigDecimal](http://docs.oracle.com/javase/6/docs/api/java/math/BigDecimal.html#compareTo(java.math.BigDecimal)) is an example where it's not consistent. – Bhesh Gurung Mar 22 '13 at 15:02

3 Answers3

12

The documentation for Comparable explains this in some detail:

The natural ordering for a class C is said to be consistent with equals if and only if e1.compareTo(e2) == 0 has the same boolean value as e1.equals(e2) for every e1 and e2 of class C. Note that null is not an instance of any class, and e.compareTo(null) should throw a NullPointerException even though e.equals(null) returns false.

It is strongly recommended (though not required) that natural orderings be consistent with equals. This is so because sorted sets (and sorted maps) without explicit comparators behave "strangely" when they are used with elements (or keys) whose natural ordering is inconsistent with equals. In particular, such a sorted set (or sorted map) violates the general contract for set (or map), which is defined in terms of the equals method.

For example, if one adds two keys a and b such that (!a.equals(b) && a.compareTo(b) == 0) to a sorted set that does not use an explicit comparator, the second add operation returns false (and the size of the sorted set does not increase) because a and b are equivalent from the sorted set's perspective.

Virtually all Java core classes that implement Comparable have natural orderings that are consistent with equals. One exception is java.math.BigDecimal, whose natural ordering equates BigDecimal objects with equal values and different precisions (such as 4.0 and 4.00).

Community
  • 1
  • 1
NPE
  • 486,780
  • 108
  • 951
  • 1,012
  • Example where it is useful to have `compareTo` inconsistent with `equals`: http://stackoverflow.com/questions/12587896/customizing-the-get-method-in-hashmap/12588005#12588005 – assylias Mar 22 '13 at 14:42
  • @NPE Can you please explain a bit this "In particular, such a sorted set (or sorted map) violates the general contract for set (or map), which is defined in terms of the equals method." with an example. I am not able to get it properly. thanks. – Trying Mar 22 '13 at 14:42
  • 3
    @Trying: From the docs for `Set`: *sets contain no pair of elements e1 and e2 such that e1.equals(e2)*. On the other hand, a `SortedSet` (which must fulfil this contract because it is-a `Set`) uses `compareTo()` instead of `equals()`. Thus if your objects don't implement `compareTo/equals` consistently, and you put them in a `SortedSet`, you'll force the latter to break its contract. – NPE Mar 22 '13 at 14:53
3

Although the documentation says that consistency is not mandatory, it is better to always ensure this consistency, as you never know whether your object may be one day present in a TreeMap / TreeSet or the like. If compareTo() returns 0 for 2 objects that are not equal, then all Tree based collections are broken.

For example, imagine a class Query, implementing an SQL query, with 2 fields:

  • tableList: list of tables
  • references: list of programs using such a query

Let's say that 2 objects are equal if their tableList is equal, i.e. the tableList is the natural key of this object. hashCode() and equals() only consider the field tableList:

public class Query implements Comparable {
    List<String> tableList;
    List<String> references;

    Query(List<String> tableList, List<String> references) {
        this.tableList = tableList;
        this.references = references;
        Collections.sort(tableList); // normalize
    }

    @Override
    public int hashCode() {
        int hash = 5;
        hash = 53 * hash + Objects.hashCode(this.tableList);
        return hash;
    }

    @Override
    public boolean equals(Object obj) {
        if (obj == null) {
            return false;
        }
        if (getClass() != obj.getClass()) {
            return false;
        }
        final Query other = (Query) obj;
        return Objects.equals(this.tableList, other.tableList);
    }
}

Let's say that we would like the sorting to be along the number of references. Writing the code naively yields a compareTo() methods which could look like this:

public int compareTo(Object o) {
    Query other = (Query) o;
    int s1 = references.size();
    int s2 = other.references.size();
    if (s1 == s2) {
        return 0;
    }
    return s1 - s2;
}

Doing so seems OK as equality and sorting are done on two separate fields, so far so good.

However, whenever is put in a TreeSet or a TreeMap, it is catastrophic: the implementation of these classes consider that if compareTo returns 0, then the elements are equal. In this case, it would mean that each object with identical number of references are indeed 'equal' objects, which is obviously not the case.

A better compareTo() method could be:

public int compareTo(Object o) {
    Query other = (Query) o;
    // important to match equals!!!
    if (this.equals(other)) {
        return 0;
    }
    int s1 = references.size();
    int s2 = other.references.size();
    if (s1 == s2) {
        return -1; // not 0, they are NOT equal!
    }
    return s1 - s2;
}
Unihedron
  • 10,902
  • 13
  • 62
  • 72
Ngx472
  • 147
  • 1
  • 4
1

Some collections will assume that if two objects follow obj1.compareTo(obj2) = 0 then obj1.equals(obj2) is also true. For example: sorted TreeSet. Failing to meet this logic will result in an iconsistent collection. See: Comparator and equals().

Community
  • 1
  • 1
omer schleifer
  • 3,897
  • 5
  • 31
  • 42