Supplementary Java documentation

As in any piece of software, there are lots of snags in the Java class libraries that you'll eventually run into when you're not satisfied with building hello world software. I maintain a list of things I've found here.

How to get macros

Sometimes, you really want to have macros in Java, for example when you want to do platform-conditional compiling (such as JRE/J2ME or version conditional code). The usual method in Java is to generate the appropriate code using a hand-made code generation class, effectively emulating macro functionality.

However, since Java looks very much like C or C++, your average C preprocessor (cpp) can also be used on Java programs. With cpp, you can just add C-like macros (#defines) in your code. Give your macro-enhanced files a separate extension, such as .javam. Now, simply run your .javam sources through cpp -P -C to obtain .java files. The switches given here are for the Gnu version of cpp. P means: leave out line number directives. C means: do not strip the comments (because javadoc uses them).

I've made a javac front-end for processing both macros and line directives: javamc.

Getting a class

There's a getClass() method for getting the Class object of a given object, but this means you have to have an instance of the object first. But what if you just want to compare a Class variable to an existing class (like instanceof but without having an instance) or you want the Class object of a static class (i.e. a class without instances)?

Something I've found out much later is there is a special language construct for getting the class object of a given class: <classname>.class. So, if we want the class object of, say, System:

Class systemclass = System.class;
The notation suggests that class is actually a public static field of Object, but this is not so. If you want the class field of an object, use getClass().

Loading a file from the classpath or a JAR file

Both Class and Classloader have a method getResourceAsStream(String) for opening a file from the class path.

Loading a class

One obvious method to do this is:
Class cls = getClass().getClassLoader().loadClass(classname);
However, this does not work on some systems, as is in fact specified in the javadocs. It works in Linux java 1.2.2, but in FreeBSD java 1.1.8, the class loader may be null because the default class loader is signified by a null. A better method is:
Class cls = Class.forName(classname);

Inputstream.read

While the documentation suggests otherwise, the inputstream.read(byte[],...) method does not try to read as many bytes as possible. It may in fact read any number of bytes it desires (as long as it is at least one), even when EOF is not reached. Some implementations will always read all characters unless EOF or an error occurred, but other implementations, such as when loading from a jar, do not.

Though this is officially considered a feature, this is quite unlike the semantics of the C fread() or Perl read(), and makes life harder for the programmer. So I think this is a bug.

PipedInputStream and PipedOutputStream.

These two classes have an odd side effect: if you create the streams in the main() thread, and you run off your main() method, the pipe times out after about a second, even if you still have some (non-daemon) threads running with references to the streams. You then get a java.io.IOException: Pipe broken if you try to read from the PipedInputStream.

ObjectInputStream and ObjectOutputStream.

If you try to execute the following code with a input and output stream that are somehow coupled:
    localin = new ObjectInputStream(input_stream);
    localout= new ObjectOutputStream(output_stream);
you get some kind of deadlock. The following will work:
    localout= new ObjectOutputStream(output_stream);
    localin = new ObjectInputStream(input_stream);

Object stream caching

Object streams maintain an internal cache of the objects they have already read/written. This means that any object that has been transferred once does not have to be transferred a second time. However, it does not keep track of changes during the lifetime of an object. This means that, if you send an object, make a change in it, and send it again, the other end gets the old object. To ensure correct behaviour, you should use immutable objects or take care that you do not accidentally change the objects.

Unfortunately, the object stream does not flush any of its cache, which means that it just keeps growing and growing when you send more objects over it, even if they are never referred to again on the sender's side. This is in fact a kind of memory leak. This means that you have to call the reset() method periodically to ensure that you do not run out of memory. Unfortunately, reset() is too slow to call every time you send an object.

ValueObjectOutputStream: a modified version of the stream without the memory leak.

Object stream versioning

One thing I've run into and have not been able to solve is versioning problems in object streams. If we send an object over an object output / input stream pair, then re-compile the class of the object, then send an object with the new class, the recipient gets a version error. Perhaps something might be done with using a new classloader to reload the class, but there are no hooks in ObjectInputStream to achieve this. It appears the class version is checked before any of the regular user-overridable methods in this class are called.

Version differences in no-arg constructor of ObjectOutputStream

The no-arg constructor is not available for subclasses in Java 1.1

Version differences in Vector.equals()

I spent two hours chasing a bug in my program which was due to an undocumented difference in semantics between the Java 1.1.5 and Java 1.2 Vector class. In Java 1.2, equals() is defined in terms of the Vector's elements. In Java 1.1.5, equals() apparently always returns false unless the two references being compared actually point to the same object.

Dimension.equals()

The docs say it should compare contents, but actually, it doesn't.

Applet behaviour in different browsers

In Netscape 4, applets keep running until they fall off the history list. It is possible to have multiple instances of the same applet if the same web page is displayed in different windows or occurs multiple times in a history list.

In IE, applets are destroyed as soon as the page they are on is no longer viewed. When opening the same page in multiple windows, the windows are just different views upon the same applets.

The above behaviour has not been tested with HotJava. HotJava is different from the other browsers, though, in a different way. The applets in different frames or pages cannot access each other. In the other browsers, it was possible to know all the applets in the browser by having them communicate through static variables. In HotJava, it appears that each page is loaded by a different classloader.

Image processing

See the ImageUtil class in JGame.