java.sql.Date vs java.sql.Timestamp
Recently I encountered a nasty little runtime bug in java. I call it a bug even though the java.sql.Timestamp javadoc has a Note explaining why.
The bug
If you run this particular JUnit method, it will fail on the last assertion.
public void testTimestampVsDate() {
java.util.Date date = new java.util.Date();
java.util.Date stamp =
new java.sql.Timestamp(date.getTime());
assertTrue(date.equals(stamp));
assertTrue(date.compareTo(stamp) == 0);
assertTrue(stamp.compareTo(date) == 0);
assertTrue(stamp.equals(date));
}
That's right.. in pseudo code:
- date equals() stamp
- date compareTo() stamp is zero (equal)
- stamp compareTo() date is zero (equal)
- stamp does not equal date!
If you didn't know already, java.sql.Timestamp extends java.util.Date. So one would think that equality would be symmetric and compareTo() == equals(). One would be wrong!
Sun's explanation
In the javadoc for java.sql.Timestamp, it states:
Due to the differences between the Timestamp class and the java.util.Date class mentioned above, it is recommended that code not view Timestamp values generically as an instance of java.util.Date. The inheritance relationship between Timestamp and java.util.Date really denotes implementation inheritance, and not type inheritance.
The last sentence is where Sun admits that they are really breaking the spirit of inheritance.. implementation inheritance, and not type inheritance.
Why this is painful
This feature can be accidentally stumbled upon (like I just did) pretty easily.
Let's say I hook up some object persistence framework to a database. Hibernate is a good example. In one of my hibernate managed objects there is a java.util.Date instance variable (databaseDate) which maps to a TIMESTAMP field in the database. Pretty common.
Now later on I want to see if that object is relevant for a date that was typed into a web form by an end user (userDate). Again, pretty common. At some point, I'm going to be checking if the dates match. If I use userDate.equals(databaseDate) everything will work as expected. This is because the java.util.Date.equals() method will be used. On the other hand, if I happen to use databaseDate.equals(userDate), nothing will ever evaluate to true.
What is so horrible is that, at compile time, I am working with two java.util.Date objects and all seems right with either comparison. And I am working with two Date objects because java.sql.Timestamp extends java.util.Date! But at runtime, I am left scratching my head.. why would date1 equal date2 but date2 not equal date1?
Now I know why.. Sun decided that implementation inheritance was how this one class out of a million was going to work. I wonder how many other inheritance structures are bastardized in the language?
