JAVA FAIL

Thursday, December 18, 2008

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:
Note: This type is a composite of a java.util.Date and a separate nanoseconds value. Only integral seconds are stored in the java.util.Date component. The fractional seconds - the nanos - are separate. The Timestamp.equals(Object) method never returns true when passed an object that isn't an instance of java.sql.Timestamp, because the nanos component of a date is unknown. As a result, the Timestamp.equals(Object) method is not symmetric with respect to the java.util.Date.equals(Object) method. Also, the hashcode method uses the underlying java.util.Date implementation and therefore does not include nanos in its computation.

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.

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 this Timestamp object to the given Date, which must be a Timestamp object. If the argument is not a Timestamp object, this method throws a ClassCastException object. (Timestamp objects are comparable only to other Timestamp objects.)

So if you pass a Date to the compareTo(Date) method, it throws. Madness.

Friday, November 21, 2008

SWITCH FAIL

public class Foo
{
public void foo(final long value)
{
switch(value) {
case 0:
break;
default:
break;
}
}
}


$ javac -Xlint:all Foo.java
Foo.java:5: possible loss of precision
found : long
required: int
switch(value) {
^
1 error


The argument to switch() must be an int, promotable to int, or an enumeration. So long can't be used. Good thing Java doesn't have to worry about things like handling 64-bit values or anything.

Thursday, November 13, 2008

JUNIT FAIL

JUnit version 4.1
Could not find class: TestX

Time: 0.001

OK (0 tests)


Maybe, just maybe, if there is an error starting the test, that should be considered a failed test?

Thursday, November 6, 2008

INTERFACE ENCAPSULATION FAIL

Let's say we have some existing useful library classes:

class LowLevelException extends Exception
{
private static final long serialVersionUID = 1L;
}

class Base
{
// Connect to the resource or throw if we can't
public Base(final String resourceName)
throws LowLevelException
{ /*...*/ }
}


Now, let's use that nice code to build some higher-level library

class Foo extends Base
{
// A higher-level API that can work with a preferred resource, but if it
// can't get it, will take a lesser-quality resource.
public Foo()
throws LowLevelException
{
try {
// Try to get the good stuff
super("Preferred Resource");

} catch(final LowLevelException ex) {
// Get by with this
super("Fall-back Resource");
}
}
}


But we can't do that:

$ javac -Xlint:all Foo.java
Foo.java:18: cannot find symbol
symbol : constructor Base()
location: class Base
{
^
Foo.java:21: call to super must be first statement in constructor
super("Preferred Resource");
^
Foo.java:25: call to super must be first statement in constructor
super("Fall-back Resource");
^
3 errors


Zomg! So this means you can't catch exceptions from your base class constructor(s) and deal with them locally?

It would seem that it also prevents you from translating an exception from a base class constructor in your derived class constructor.

class HighLevelException extends Exception
{
private static final long serialVersionUID = 1L;
}

class Foo2 extends Base
{
// A higher-level API that has some better way to represent the low-level
// exception, perhaps simply by adding more detail about the failure.
public Foo2()
throws HighLevelException
{
try {
super("Something");

} catch(final LowLevelException ex) {
throw new HighLevelException("More detail", ex);
}
}
}


$ javac -Xlint:all Foo.java
Foo.java:43: cannot find symbol
symbol : constructor Base()
location: class Base
{
^
Foo.java:45: call to super must be first statement in constructor
super("Something");
^
Foo.java:48: cannot find symbol
symbol : constructor HighLevelException(java.lang.String,LowLevelException)
location: class HighLevelException
throw new HighLevelException("More detail", ex);
^
3 errors


Yeah, cause why would I ever want to do that? I dunno, perhaps to make a clean API encapsulates its dependencies? *sigh*

ORTHOGONALITY FAIL

class Foo extends String
{
// This avoids a warning
private static final long serialVersionUID = 1L;
}


$ javac -Xlint:all Foo.java
Foo.java:1: cannot inherit from final java.lang.String
class Foo extends String
^
1 error


I know what final classes are. I think they're the wrong solution to any problem, but I know what they are.

But what is the argument for making String a final class? And if the reason is "because String is special and the compiler knows about it and blah blah", it shouldn't be.

Tuesday, November 4, 2008

ENCAPSULATION FAIL

Nope, can't do this:

import java.util.regex.Pattern;
class foo
{
public static void main(String[] args)
{
static final Pattern regex = Pattern.compile("abc");
}
}



Now I have to go put it somewhere and document it in more detail instead of just keeping it by its only point of use.

LEXER FAIL


class foo
{
public static void main(String[] args)
{
// This prints: \uXXXX
System.out.println("\\uXXXX");
}
}




$ javac -Xlint:all foo.java
foo.java:5: illegal unicode escape
// This prints: \uXXXX
^
1 error



No, Java. You shouldn't look for escape sequences in comments. Why are you processing string literal escapes there? Why are you even doing it at that stage of processing? C'mon.