madbean

Two things I hate about Java I/O

08 Dec 2004

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("&amp;");
    } else if (c == '<') {
      out.write("&lt;");
    } else if (c == '>') {
      out.write("&gt;");
    } 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.

  • Home
  • Blog