While I’m in a bile-laden mood (see previous entry), here are two things I think were horribly mis-designed in the Java I/O API.
Subclassing java.io.FilterWriter
(This rant also applies to FilterReader, FilterInputStream
and FilterOutputStream.)
I commonly need to apply a filter or transformation when writing out some data. A common example might be XML-escaping. You would think that FilterWriter would be the perfect class to subclass. It’s javadoc even says:
Abstract class for writing filtered character streams. [...] Subclasses of FilterWriter should override some of these methods and may also provide additional methods and fields.
If you wanted to write an XML-escaping writer, and you kind-of know how the java.io classes work,
you would think that all you need to do is subclass FilterWriter and override
write(char cbuf[], int off, int len). Something like this (inefficient) implementation:
public void write(char cbuf[], int off, int len) throws IOException {
for (int i = off; i < off + len; i++) {
char c = cbuf[i];
if (c == '&') {
out.write("&");
} else if (c == '<') {
out.write("<");
} else if (c == '>') {
out.write(">");
} else {
out.write(c);
}
}
}
At least, that is how I would write it, because my journeys through the java.io API
has taught me that write(char cbuf[], int off, int len) is the "real" write method
(and it is abstract in Writer). All the other write(...) methods
end up calling the "real" one.
But that is wrong, wrong, wrong for FilterWriter! It overrides all the
write(...) methods so that they call through to the equivalent method on the out
variable. My code above is wrong: if I want to write an XML-escaping writer that subclasses
FilterWriter, I must override all off the write(...) methods
and do the escaping in each one (or some similar refactoring).
You are much better off just subclassing Writer. FilterWriter is a joke.
java.io.Writer.append() in JDK1.5+
I love the CharSequence
interface introduced in 1.4. I find CharSequence particularly useful when dealing
with extremely large strings. We even have a class that maps the contents of a file as
a CharSequence: we can then pass such objects to the Java regex package, knowing
that the runtime memory footprint will only be a few KB, instead of 100's MB (for large files).
For me, classes that will accept a CharSequence are saying
"I can deal with string-like data, but I don't require it to be given
as an immutable java.util.String".
So I was happy to see that in JDK1.5 they introduced
Appendable,
and ensured that
Writer implements
Appendable.
I instantly thought to myself: I can pass one of my huge CharSequences
to Writer, and it will gracefully stream it out.
Wrong, wrong, wrong!
What do the append(...) methods in Writer do? They all call
.toString() on the CharSequence argument. Doesn't that
negate the whole point of CharSequence's existence?!?
Grrr.
