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.

7 Comments
I believe the goal of Appendable was to aid in dealing with generics. See:
http://mindview.net/WebLog/log-0061
And
http://mindview.net/WebLog/log-0064
Bruce: That is probably true, but that does not (in the least) require Writer to call .toString() on the CharSequences.
> They all call .toString() on the CharSequence argument.
> Doesn’t that negate the whole point of CharSequence’s existence?!?
What else would they do? Write a char at a time? Then almost everyone else would complain that append() is too inefficient since appending huge files via CharSequence isn’t the norm. BTW: I think CharSequence is around for Formatting, not for generics (but if it is around for generics, I’d love to know why).
> You are much better off just subclassing Writer. FilterWriter is a joke.
Well, they do provide the Writer delegate, the close, and the flush for you.
> What else would they do? Write a char at a time?
Have a look at the implementation of write(String str, int off, int len) in Writer… Writer already has a char[] writeBuffer it uses to efficiently write out a String, that exact same code could be used for a CharSequence (replacing String with CharSequence) (infact, write(String) could then just call write(CharSequence) without a loss of semantics).
If FilterWriter meant you yo override one method, the other write methods would have declared final. You are relying on some observed behaviour with another class and assuming that just because some other class implemented those methods to delegate to the single method that everything else should. In fact your code previously sounds like it is wrong. What would happen on the IBM JDK? GNU Classpath?
Well… OK, but I don’t see a huge win. Writer.write(String str, int off, int len) only uses that writeBuffer if the len is 1024 or less. But your suggestion does eliminate a double allocation of len chars. Currently, one array is allocated backing the String creation in in append() and another is the array allocated in that write() call (to be used instead of the writeBuffer). So even with your suggestion, depending upon the size of your file, you’ll be in trouble, but at least you won’t be in double trouble.
Hey, your idea seems like the perfect RFE/bug request for Mustang (or even JDK 1.5.next_minor). It’d be a kinda minor enhancment for most usage, but every bit counts.
In any case though, it seems to me you’ll have to override append to read from CharSequence and write a block at a time.
> If FilterWriter meant you yo override one method
I was going to mention the same thing, except that FilterWriter misses a couple other write methods. So, someone could extend your class, or change the behavior of those other methods and you’d be in the same boat.
OTOH, designing for inheritence is tough and this stuff is from JDK 1.1. I do hope that someone goes though and defines exactly how these classes should be extended, like we have in the Collections classes.