I have a Map
, and a list of objects from the database with an effectiveDate
property, and I want to check to see
Part 1: "Shouldn't equals
agree with compareTo
?*
No; compareTo should agree with equals, but the reverse is irrelevant.
compareTo is about sorting order. equals is about equality. Consider race cars, that may be sorted by fastest lap time in practice to determine starting position. Equal lap times does not mean they are the same car.
Part 2: Equal dates.
Database calls will return a java.sql.Date, which although it is assignable to java.util.Dare, because it extends that, will not be equal, because the class is different.
A work around may be:
java.util.Date test;
java.sql.Date date;
if (date.equals(new java.sql.Date(test.getTime()))
As Mureinik hinted at and Sotirios Delimanolis pointed out more specifically, the problem here is with the implementation of java.util.Date
.
java.util.Date
is extended by 3 classes in the java.sql
package, all of which seem to do similar things and whose distinction in java is not at all clear (seems like the reason for their existence is simply to make java classes which align more accurately to SQL datatypes) - for more information on their differences, check out this very detailed answer.
Now, in what seems like a serious design flaw, someone decided to make equals() asymmetric with java.sql.Timestamp - that is, timestamp.equals(date)
could return false even if date.equals(timestamp)
returns true. Great idea.
I wrote a few lines to see which java.sql
classes demonstrate this ridiculous property - apparently it's just Timestamp
. This code:
java.util.Date utilDate = new java.util.Date();
java.sql.Date sqlDate = new java.sql.Date(utilDate.getTime());
System.out.println("sqlDate equals utilDate:\t" + sqlDate.equals(utilDate));
System.out.println("utilDate equals sqlDate:\t" + utilDate.equals(sqlDate));
java.sql.Time time = new java.sql.Time(utilDate.getTime());
System.out.println("time equals utilDate:\t\t" + time.equals(utilDate));
System.out.println("utilDate equals time:\t\t" + utilDate.equals(time));
java.sql.Timestamp timestamp = new java.sql.Timestamp(utilDate.getTime());
System.out.println("timestamp equals utilDate:\t" + timestamp.equals(utilDate));
System.out.println("utilDate equals timestamp:\t" + utilDate.equals(timestamp));
Yields this:
sqlDate equals utilDate: true
utilDate equals sqlDate: true
time equals utilDate: true
utilDate equals time: true
timestamp equals utilDate: false
utilDate equals timestamp: true
Since java.util.HashMap uses parameter.equals(key) in it's implementation of containsKey() (rather than key.equals(parameter)
), this one strange result shows up in the given situation.
So, how to get around this?
1) Use a Long
key in the map rather than a Date
(as Mureinik noted) - since java.util.Date
and java.util.Timestamp
return the same value from getTime()
, it shouldn't matter which implementation you're using, the key will be the same. This way does seem like the simplest.
2) Standardize the date object before using it in the map. This way requires a tiny bit more work, but to me seems more desirable as it's more clear what the map is - a bunch of Foo
each stored against a moment in time. This is the way I ended up using, with the following method:
public Date getStandardizedDate(Date date) {
return new Date(date.getTime());
}
It takes an extra method call (and kind of a ridiculous one at that), but to me the increased readability of the code involving the Map<Date, Foo>
is worth it.
A Date
object returned from a database would probably be a java.sql.Timestamp
, which cannot be equal to a java.util.Date
object. I'd just take the long returned from getTime()
, and use that as a key in your HashMap
.