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.