What better way to learn about Java than to open the hood and take a look inside. Tonight we look inside the Writer class.

I set out to write about the Console class and then got swept up in the excitement buried in the Writer class and so I’m starting with that. More to follow on both of them, though.

To get at the source for the Writer class, I navigated to the declaration in Intellij using ⌘ + B to get to:

/Library/Java/JavaVirtualMachines/jdk1.7.0_55.jdk/Contents/Home/src.zip!/java/io/Writer.java

More information about where Java lives on OS X can be found from Apple here and from Oracle here. Note that there is generally an older system installation that is separate from the more modern JDK you probably have installed.

The Writer class is the superclass for BufferedWriter, CharArrayWriter, FilterWriter, OutputStreamWriter, PipedWriter, PrintWriter, StringWriter.

At it’s core, Writer wraps a character buffer that is inaccessible to the client:

public abstract class Writer implements Appendable, Closeable, Flushable {
  private char[] writeBuffer;
  private final int writeBufferSize = 1024;

Both the Writer and Reader abstract classes are meant to be used with character data only, whereas the abstract InputStream and OutputStream are for binary data (in bytes).

Writer is abstract, though it provides default implementations of several methods that use this character buffer, for example:

public void write(int c) throws IOException {
    synchronized (lock) {
        if (writeBuffer == null){
            writeBuffer = new char[writeBufferSize];
        }
        writeBuffer[0] = (char) c;
        write(writeBuffer, 0, 1);
    }
}

and

public void write(String str, int off, int len) throws IOException {
        synchronized (lock) {
            char cbuf[];
            if (len <= writeBufferSize) {
                if (writeBuffer == null) {
                    writeBuffer = new char[writeBufferSize];
                }
                cbuf = writeBuffer;
            } else {    // Don't permanently allocate very large buffers.
                cbuf = new char[len];
            }
            str.getChars(off, (off + len), cbuf, 0);
            write(cbuf, 0, len);
        }
    }

All of the default methods ultimately call the abstract method write:

abstract public void write(char cbuf[], int off, int len) throws IOException;

which is obviously implementation dependent. However, in the case of the write(char) (and consequently append(char) which calls write(char)) the character buffer is written to first and then the abstract method is called. I’m not sure why; this seems like it could lead to inconsitent behavior, but probably just helps to emphasize that this class isn’t meant to be used directly.

The append methods all call their write counterparts, but return Writer objects rather than void, so they can be chained. Also, in the event that append(CharSequence) is called with append(null), the null will be converted to a string before calling write(String).

More thoughts about the difference between the write and append methods can be found here. Among other things, the append methods provide an implementation for the Appendable interface:

public interface Appendable {
    Appendable append(CharSequence csq) throws IOException;
    Appendable append(CharSequence csq, int start, int end) throws IOException;
    Appendable append(char c) throws IOException;
}

There is lots more happening at the implementation level. In a future post, I’ll investigate a concrete implementation of Writer, PrintWriter. A good starting place this can be found here.