equals() method and the
compareTo() method on
two of the most basic in Java. Yet their definitions have an interesting wrinkle around the concept
of "consistent with equals".
The equals() method
equals() method is both well defined and unclear in Java.
It is well-defined in that it specifies exactly how the check for equal must work.
They must be reflexive, symmetric, transitive, consistent and handle null.
equals() is also unclear.
The Javadoc says that the method "Indicates whether some other object is "equal to" this one".
Note the "equal to" part is in quotes.
The key here is that it does not define how the equality should be determined.
There are three standard choices:
- the identity of the object (
==), which is the default inherited from
- the entire observable state of the object, such that if two objects are equal then one is substitutable for the other in other parts of the application
- some subset of the information that makes logical sense to check equals on, such as an ID
The compareTo() method
Comparable interface defines the concept of comparability.
specifies that it "imposes a total ordering on the objects of each class that implements it".
Classes that implement
Comparable have a natural ordering and can be sorted easily and
used in collections like
TreeMap without a separate
The interface is well-defined, in that it specifies that the implementation must ensure a form of symmetry
and transitive behaviour, just like
It does not specify exactly how the comparison should be made, but defines the concept of consistency with equals.
Consistent/Inconsistent with equals
Comparable interface says the following:
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
for every e1 and e2 of class C.
Basically, this requires that the concept of equals defined by
compareTo is the same
as that defined by
equals() (apart from nulls).
This is at first glance a simple requirement but, as I'm going to discuss, it has complications.
This kind of definition is especially useful when considering operator overloading.
If we consider a Java-like language where
== is not object identity, but comparison using a method,
and the greater-than/less-than operators are also available the question is what method to call.
Greater-than/less-than would in this Java-like language naturally be based on
== would call
// our new Java-like language if (a < b) return "Less"; // translation ignoring nulls: if (a.compareTo(b) < 0) if (a > b) return "Greater"; // translation ignoring nulls: if (a.compareTo(b) > 0) if (a == b) return "Equal"; // translation ignoring nulls: if (a.equals(b)) throw new Exception("Impossible assuming no nulls?");
compareTo() is "inconsistent with equals" then this code can throw the exception,
a.compareTo(b) can return zero when
a.equals(b) is false.
Other problems can occur in collections like
// Foo class is "inconsistent with equals" assert foo1.equals(foo2) == false; assert foo1.compareTo(foo2) == 0; TreeMap<Foo, String> map = ... map.put(foo1, "a"); map.put(foo2, "b");
Here the two objects are not equal using
equals() but are equal using
In this case, the map will have size one, not size two!
Because of the problems with being "inconsistent with equals", the Javadoc says It is strongly recommended (though not required) that natural orderings be consistent with equals.
Many JDK classes implementing
Comparable do so in a manner "consistent with equals".
Some others are more interesting:
- BigDecimal - well-known to be "inconsistent with equals", as 4.00 is not equal to 4.0, but do compare the same
- Double/Float - provides an explicit sort order and equals check for positive and negative zero and NaN to ensure compareTo is "consistent with equals"
- CharSet - based on the ID/name. equals is case-senstitive, while compareTo is not, although as the names are normalized it tends not to matter - dubiously "consistent"
- *Buffer (nio) - based on remaining content of the buffer. equals and compareTo appear to be "consistent" under my testing
- Rdn (ldap) - based on the normalized form of the state, thus is "consistent with equals"
- ObjectStreamField (serialization) - based on the name, but with primitives sorted first. equals is not overriden, so is "inconsistent with equals"
- ObjectName (jmx) - equals based on the canonical name, compareTo defined as "not completely specified but is intended to be such that a sorted list of ObjectNames will appear in an order that is convenient for a person to read"! "inconsistent with equals"
- FileTime (io) - implements equals using compareTo, definitely "consistent with equals"
- File (io) - OS-specific, implements equals using compareTo, definitely "consistent with equals"
- URI - source code appears to be "consistent with equals"
- UUID - source code for equals includes variant that compareTo doesn't, but it appears to be unessesary code with no effect such that it is "consistent with equals"
- Date (java.util) - source code difficult to follow because equals changes the state of the object (normalizing the deprecated setters), whereas compareTo does not change the state. As both ultimately so the same calculation it is "consistent with equals"
- Date/Time (sql) - subclass java.util.Date and add nothing new, so are also "consistent with equals"
- Timestamp (sql) - horrible compareTo and equals implementations that break the contracts, but is "consistent with equals" when comparing two Timestamp objects
- Calendar/GregorianCalendar - equals compares the state, while compareTo compares along the time-line. Definitely "inconsistent with equals"
Note: In almost all cases, I had to examine the source code or write test code to determine whether a class was or was not "consistent with equals". There is a good Adopt-a-JDK task here to clarify the Javadoc and check UUID equals.
Basically, it seems really simple to define equals/compareTo for some classes.
LocalDate is just a date in a single calendar system, so has an obvious ordering and equals.
LocalTime is just a time, so has an obvious ordering and equals.
Instant is just an instantaeous point on the time-line, so has an obvious ordering and equals.
However other cases are not so obvious. Consider
dt1 = OffsetDateTime.parse("2012-11-05T06:00+01:00"); dt2 = OffsetDateTime.parse("2012-11-05T07:00+02:00");
These two date-time objects represent the same instantaneous point on the time-line. But they have different local times and different offsets from UTC/Greenwich.
So, one question to readers.... which of these options do you prefer...
- dt1 not equal to dt2,
compareTo()takes into account the local time and offset, "consistent with equals" (with a separate
Comparatorto sort by instant)
- dt1 not equal to dt2,
compareTo()based on the instantaneous point on the time-line, "inconsistent with equals"
- dt1 equal to dt2,
compareTo()based on the instantaneous point on the time-line, "consistent with equals"
- dt1 equal to dt2 and do not implement Comparable
- dt1 not equal to dt2 and do not implement Comparable
Personally I struggle with the idea that
dt1.equals(dt2) would be true, but I'm open to opinions.
BTW, the question could also be posed with respect to
If you could to make that class "consistent with equals" would you change the
equals() method or