IS-A FAIL
I knew this was wrong the first time I saw it. After having the chance to work with the java.sql.Timestamp class for a while, I'm just left shaking my head. You can probably figure out what's wrong just by reading the class documentation, but here's a synopsis.
The java.util.Date class is the "normal" timestamp class in the Java standard library. It's basically a wrapped up POSIX timestamp, except that 64-bit signed value is an expression of the number of milliseconds since the epoch instead of the usual seconds. A strange choice to begin with, but not really unreasonable.
Databases may represent timestamps to a higher resolution than milliseconds, in the general case. When interacting with a database with JDBC, one doesn't want to lose precision on timestamp values merely because of Date's chosen resolution. JDBC chose to address this by introducing the Timestamp class, which records times to nanosecond resolution. Presumably since most of the behaviour and and interface on Date also make sense for Timestamp, Timestamp derives from Date. But hold on, take a look at that note on Timestamp:
Woah! So, Timestamp is-not-a Date! This note is essentially a warning to not treat a Timestamp polymorphically as if it were a Date. Now this isn't in itself always necessarily evil. Although, in C++, you might think about using private or protected inheritance with some using declarations, or aggregation. In Java, you're stuck with the choice of aggregation and re-offering a semantically-different-but-syntactically-the-same interface on Timestamp. If a Timestamp is not usable as a Date, then aggregation seems like the right choice here.
But wait a minute. Is there really a need for Timestamp to be unusable as a Date? Why did they chose to stop storing the milliseconds of the timestamp value in the Date part of the object? That decision doesn't really make any sense. Why wouldn't you just leave the Date part of the class as-is and allow it to store whole milliseconds, and then have an additional field that stores the fractional milliseconds (down to the resolution of nanoseconds)?
This wouldn't require any changes in the Timestamp interface at all. The class could still offer getTime() and getNanos() methods and so on cheaply, and, critically, it would then be true that a Timestamp is-a Date! You could compare two Timestamp values through their Date.equals() interface (since the Timestamp version would be called). You could even compare Date values to Timestamp values with reasonably semantics: if you use the Date.equals(Timestamp) interface, you just ignore the fractional part, which is presumably what you meant, since you used an interface that has nothing to do with fractional parts; if you use the Timestamp.equals(Date) interface, it can only be true if the Timestamp has zero fractional milliseconds.
Alas, this is not the case, which makes the Timestamp class far less useful
And, in case you're not yet convinced that this is complete madness, check out the compareTo(Date) method, which states:
So if you pass a Date to the compareTo(Date) method, it throws. Madness.
The java.util.Date class is the "normal" timestamp class in the Java standard library. It's basically a wrapped up POSIX timestamp, except that 64-bit signed value is an expression of the number of milliseconds since the epoch instead of the usual seconds. A strange choice to begin with, but not really unreasonable.
Databases may represent timestamps to a higher resolution than milliseconds, in the general case. When interacting with a database with JDBC, one doesn't want to lose precision on timestamp values merely because of Date's chosen resolution. JDBC chose to address this by introducing the Timestamp class, which records times to nanosecond resolution. Presumably since most of the behaviour and and interface on Date also make sense for Timestamp, Timestamp derives from Date. But hold on, take a look at that note on Timestamp:
Note: This type is a composite of ajava.util.Date
and a separate nanoseconds value. Only integral seconds are stored in thejava.util.Date
component. The fractional seconds - the nanos - are separate. TheTimestamp.equals(Object)
method never returnstrue
when passed an object that isn't an instance ofjava.sql.Timestamp
, because the nanos component of a date is unknown. As a result, theTimestamp.equals(Object)
method is not symmetric with respect to thejava.util.Date.equals(Object)
method. Also, thehashcode
method uses the underlyingjava.util.Date
implementation and therefore does not include nanos in its computation.
Due to the differences between theTimestamp
class and thejava.util.Date
class mentioned above, it is recommended that code not viewTimestamp
values generically as an instance ofjava.util.Date
. The inheritance relationship betweenTimestamp
andjava.util.Date
really denotes implementation inheritance, and not type inheritance.
Woah! So, Timestamp is-not-a Date! This note is essentially a warning to not treat a Timestamp polymorphically as if it were a Date. Now this isn't in itself always necessarily evil. Although, in C++, you might think about using private or protected inheritance with some using declarations, or aggregation. In Java, you're stuck with the choice of aggregation and re-offering a semantically-different-but-syntactically-the-same interface on Timestamp. If a Timestamp is not usable as a Date, then aggregation seems like the right choice here.
But wait a minute. Is there really a need for Timestamp to be unusable as a Date? Why did they chose to stop storing the milliseconds of the timestamp value in the Date part of the object? That decision doesn't really make any sense. Why wouldn't you just leave the Date part of the class as-is and allow it to store whole milliseconds, and then have an additional field that stores the fractional milliseconds (down to the resolution of nanoseconds)?
This wouldn't require any changes in the Timestamp interface at all. The class could still offer getTime() and getNanos() methods and so on cheaply, and, critically, it would then be true that a Timestamp is-a Date! You could compare two Timestamp values through their Date.equals() interface (since the Timestamp version would be called). You could even compare Date values to Timestamp values with reasonably semantics: if you use the Date.equals(Timestamp) interface, you just ignore the fractional part, which is presumably what you meant, since you used an interface that has nothing to do with fractional parts; if you use the Timestamp.equals(Date) interface, it can only be true if the Timestamp has zero fractional milliseconds.
Alas, this is not the case, which makes the Timestamp class far less useful
import java.util.Date;
import java.sql.Timestamp;
public class Foo
{
public static void main(final String[] args)
{
System.out.println("D == T: "
+ (new Date(1000)).equals(new Timestamp(1000)));
System.out.println("T == D: "
+ (new Timestamp(1000)).equals(new Date(1000)));
}
}
$ java Foo
D == T: true
T == D: false
And, in case you're not yet convinced that this is complete madness, check out the compareTo(Date) method, which states:
Compares thisTimestamp
object to the givenDate
, which must be aTimestamp
object. If the argument is not aTimestamp
object, this method throws aClassCastException
object. (Timestamp
objects are comparable only to otherTimestamp
objects.)
So if you pass a Date to the compareTo(Date) method, it throws. Madness.