Wednesday, 19 September 2012

Puzzle 81: Charred Beyond Recognition


This program appears to do the usual thing in an unusual way. What does it print?
public class Greeter {

    public static void main (String[] args) {

        String greeting = "Hello world";

        for (int i = 0; i < greeting.length(); i++)

            System.out.write(greeting.charAt(i));

    }

}


Solution 81: Charred Beyond Recognition

Although it's a bit strange, there is little reason to suspect that this program should misbehave. It writes "Hello world" to System.out, one character at a time. You may be aware that the write method uses only the low-order byte of its input parameter. This would cause trouble if "Hello world" contained any exotic characters, but it doesn't: It consists entirely of ASCII characters. Whether you print it one character at a time or all at once, the result should be the same: The program should print Hello world. Yet, if you ran it, it almost certainly printed nothing. Where did the greeting go? Perhaps the program just wasn't feeling all that cheerful?
The problem is that System.out is buffered. The characters in Hello world were written to the buffer for System.out, but the buffer was never flushed. Most programmers believe that System.out and System.err flush themselves automatically whenever output is performed. This is almost true but not quite. They are of type PrintStream, whose documentation says, as of release 5.0 [Java-API]:
A PrintStream can be created so as to flush automatically; this means that the flush method is automatically invoked after a byte array is written, one of the println methods is invoked, or a newline character or byte ('\n') is written.
The streams referenced by System.out and System.err are indeed instances of the automatically flushing variant of PrintStream, but no mention is made of the write(int) method in the above documentation. The documentation for write(int) says: "Write the specified byte to this stream. If the byte is a newline and automatic flushing is enabled then the flush method will be invoked" [Java-API]. In practice, write(int) is the only output method that does not flush a PrintStream on which automatic flushing is enabled.
Curiously, if the program is modified to use print(char) instead of write(int), it flushes System.out and prints Hello world. This behavior contradicts the documentation for print(char), which says [Java-API]:
Print a character. The character is translated into one or more bytes according to the platform's default character encoding, and these bytes are written in exactly the manner of the write(int) method.
Similarly, if the program is modified to use print(String), it flushes the stream even though the documentation prohibits it. The documentation should almost certainly be changed to describe the actual behavior; it would be too destabilizing to change the behavior.
The simplest change that fixes the program is to add a System.out.flush invocation after the loop. If this change is made, the program prints Hello world as expected. It would, however, be far better to rewrite the program to use the more familiar System.out.println idiom for producing output on the console.
The lesson of this program is, as in Puzzle 23: Use familiar idioms whenever possible; if you must stray from familiar APIs, be sure to consult the documentation. There are three lessons for API designers: Make the behavior of your methods clear from their names, document this behavior clearly, and correctly implement the documented behavior.

No comments:

Post a Comment

Your comments are welcome!