Monday, October 11, 2010

Java Print Streams

Java Print Streams


The first two output streams that Java programmers encounter are usually instances of the java.io.Printstream class. If you want to learn more about print streams, keep reading; this is the first part of a three-part series on the topic. It is excerpted from chapter seven of Java I/O, Second Edition, written by Elliotte Rusty Harold (O'Reilly, 2006; ISBN: 0596527500). Copyright © 2006 O'Reilly Media, Inc. All rights reserved. Used with permission from the publisher. Available from booksellers or direct from O'Reilly Media.

System.out is the first output stream most Java programmers encounter. System.err is probably the second. Both are instances of thejava.io.PrintStreamclass.PrintStreamis a subclass ofFilterOutputStreamthat converts numbers and objects to text.System.outis primarily used for simple, character-mode applications and for debugging. Its raison d’étre is convenience, not robustness; print streams ignore many issues involved in internationalization and error checking. This makesSystem.outeasy to use in quick-and-dirty hacks and simple examples, while simultaneously making it unsuitable for production code, which should use thejava.io.PrintWriterclass
PrintStreamis not limited to the console.PrintStreamis a filter stream and thus can be connected to any other output stream: aFileOutputStream, aByteArrayOutputStream, aTelnetOutputStream, or anything else you write to. Three constructors can be used to chain aPrintStreamto an underlying stream:

public PrintStream(OutputStream out)
public PrintStream(OutputStream out, boolean autoFlush)
public PrintStream(OutputStream out, boolean autoFlush, String encoding)
throws UnsupportedEncodingException

Java Print Streams - Print Versus Write

:

The reason you might choose a PrintStream instead of a raw OutputStream is for its print()andprintln()methods. These methods each convert their argument to aStringand then convert theStringto bytes in a specific encoding before writing it to the underlying output stream. For example, consider thisPrintStreamconnected to a file named numbers.dat:

PrintStream out = new PrintStream(new FileOutputStream("numbers.dat"));

Suppose we use a simple for loop to write the numbers from 0 to 127 in that file:

for (int i = 0; i <= 127; i++) out.write(i);

When we’re done, the file contains 128 bytes: the first byte is 0, the second is 1, the third is 2, and so on. It’s pure binary data. If you try to open it up in a texteditor. you’ll see goop, as shown in Figure 7-1. Some of those binary numbers happen to be interpretable as ASCII text, but that’s an accident. They’re really just bytes. Many of them aren’t printable.


Figure 7-1. A binary file in a text editor

Now suppose instead of using thewrite()method we use theprint()method:

for (int i = 0; i <= 127; i++) out.print(i);

This time thePrintStreamdoes not write raw binary data in the file. Instead, it converts each number into its ASCII string equivalent and writes that string. For instance, instead of writing the byte 20, it writes the character 2 followed by the character 0. If you open the file in a text editor now, you’ll see something like the screenshot shown in Figure 7-2.


Figure 7-2. A text file in a text editor

It’s not absolutely guaranteed that the string will be written in ASCII. On a few IBM mainframes, EBCDIC might be used instead. However, given the range of characters used here, it’s pretty likely that the resulting file will make sense when interpreted as ASCII. More on this point shortly.

Theprintln()method prints a platform-specific line terminator after printing its argument. Suppose instead of using theprint()method we use theprintln()method:

for (int i = 0; i <= 127; i++) out.println(i);

Then the output is even neater, as shown in Figure 7-3.

These examples used ints, but thePrintStream class hasprint()andprintln( )methods for every Java data type. The method signatures are:

public void print(boolean b)
public void print(char c)
public void print(int i)
public void print(long l)
public void print(float f)
public void print(double d)
public void print(char[] s)
public void print(String s)
public void print(Object o)
public void println(boolean b)
public void println(char c)
public void println(int i)
public void println(long l)
public void println(float f)
public void println(double d)
public void println(char[] s)
public void println(String s)
public void println(Object o)

You can pass anything at all to aprint()method. Whatever argument you supply is guaranteed to match at least one of these methods. Object types are converted to


Figure 7-3. A text file with line breaks

strings by invoking theirtoString()methods. Primitive types are converted with the appropriateString.valueOf()methods.

One aspect of makingSystem.outsimple for quick jobs is not in thePrintStream class at all, but in the compiler. Because Java overloads the+operator to signify concatenation of strings, primitive data types, and objects, you can pass multiple variables to theprint()andprintln()methods, which are then converted to strings and concatenated. For example, consider the line:

System.out.println("As of " + (new Date()) + " there have been over "
+ hits + " hits on the web site." );

The compiler rewrites this complicated expression as:

StringBuffer sb = new StringBuffer();
sb.append("As of ");
Date d = new Date();
sb.append(d);
sb.append(" there have been over ");
sb.append(hits);
sb.append(" hits on the web site.")
String s = sb.toString();
System.out.println(s);

TheStringBuffer append()method is overloaded in much the same way that theprint()andprintln()methods are; as a result, it can handle any Java data type.

Java Print Streams - Line Breaks

:

As previously mentioned, the println() method always adds a line break at the end of each line it prints. You can even call println() with no arguments to print just a line break:

public void println()

The line break character varies from platform to platform. In particular:

On Unix (including Mac OS X), it’s a linefeed,\n, ASCII 10.
On Mac OS 9, it’s a carriage return,\r, ASCII 13.
On Windows, it’s a carriage return linefeed pair,\r\n, ASCII 13 followed by ASCII 10.
This is almost never what you actually want!

Most file formats and most network protocols care a great deal about which line break character is written.* For instance, if you’re writing a web client or server, the HTTP specification requires that header lines end with carriage return linefeed pairs. It doesn’t matter whether the client or server is a Mac, a PC, a Unix workstation, or a Palm Pilot. It must use\r\nas the line break. You can specify this by explicitly passing the line break you want to theprint()method rather than callingprintln(). For example:

for (int i = 0; i <= 127; i++) {
out.print(i);
out.print("\r\n");
}

In practice, most HTTP servers and clients accept requests that use the wrong line breaks. However, some aren’t so forgiving, and you really shouldn’t count on this behavior.

If for some reason you want to know which line break character will be used, theline.separatorsystem property will tell you:

String lineBreak = System.getProperty("line.separator");

Not all line breaks are created equal. If thePrintStream is set toautoFlush—that is, if the second argument to the constructor istrue—after every call toprintln()and after every linefeed that’s printed, the underlying stream will be flushed. Thus,out.println()andout.print("\n")both flush the stream. So doesout.print("\r\n"), because it contains a linefeed. However,out.print("\r")does not cause an automatic flush.


Java Print Streams - Error Handling

:

You may have noticed something a little funny with the code fragments in this chapter: I haven’t put any try-catch blocks around them. That’s not an oversight. PrintStream methods never throw IOExceptions. Each method in the class catchesIOException. When an exception occurs, an internal flag is set totrue. You can test this flag using thecheckError()method:

public boolean checkError()

This method returnstrueif this print stream has ever encountered an error during its lifetime. Most of the time, you just ignore this, since print streams are only used in situations where exhaustive error checking is unnecessary.

There’s also a protectedsetError()method you can use to signal an error from a subclass:

protected void setError()

Once an error has been set, there’s no way to unset it. Generally, once aPrintStreamhas encountered an error, all further writes to it silently fail. It’s not the failure but the silence that makesPrintStream unsuitable for most applications.

printf()
I was inspired to write the first edition of this book by the numerous questions I received about why there was no printf() function in Java. Part of the goal of that edition was to explain to readers why they didn’t actually need it. Thus, I was a little perturbed when Java 5 added printf(). Personally, I still don’t think Java needs printf(), but it’s here now, so let’s talk about it.

Theprintf()method makes heavy use of Java 5’s new varargs feature. That is, a single method definition can support any number of arguments. In this case, the signature is:

public PrintStream printf(String format, Object... args)

A typical invocation looks like this:

System.out.printf("There are %f centimeters in %f inches.", 2.54*inches, inches);

If you’re an old C hack, this is like coming home. The first argument is a format string containing both literal text and tags beginning with percent signs (%). To form the output, each tag is replaced by the corresponding argument that follows the format string. If the format string is the zeroth argument, the first tag is replaced by the first argument, the second tag by the second argument, and so forth. If there are more tags than arguments,printf()throws ajava.util.MissingFormatArgumentException. This is a subclass ofIllegalFormatException, which is a runtime exception, so you don’t have to catch it. If there are more arguments than tags, the extra arguments are silently ignored.

Printf()

The letter(s) after the percent sign in the format tag specify how the number is interpreted. For instance,%fmeans that the number is formatted as a floating-point number with a decimal sign.%dformats the argument as a decimal integer.%xformats the number as a hexadecimal integer.%Xalso formats the number as a hexadecimal integer but uses the uppercase letters A–F instead of the lowercase letters a–f to represent 10–15.

Most of the time, changing a lowercase conversion specifier to uppercase changes the formatted string from lowercase to uppercase. However, there are a few exceptions to this rule.

There are a couple of dozen tags for different kinds of data. Not all data is compatible. For instance, if you use%xto format adouble as a hexadecimal integer,printf()throws ajava.util.IllegalFormatConversionException. Again, this is a runtime exception and a subclass ofIllegalFormatException.

So far, this isn’t anything that can’t be done easily withprintln()and string concatenation. What makesprintf()more convenient for some uses is that the tags can also contain width and precision specifiers. For example, suppose we wrote the previous statement like this instead:

System.out.printf("There are %.3f centimeters in %.2f feet.", 2.54*feet, feet);

%.3fmeans that the centimeters will be formatted as a decimal number with exactly three digits after the decimal point.%.2fmeans that the number will be rounded to only two decimal places. This gives more legible output, like “There are 21.691 centimeters in 8.54 feet” instead of “There are 21.690925 centimeters in 8.539734 feet.”

A number before the decimal point in the format tag specifies the minimum width of the formatted string. For instance,%7.3fformats a decimal number exactly seven characters wide with exactly three digits after the decimal point. Those seven characters include the decimal point, so there will be exactly three digits to the left of the decimal point. If the number is smaller than 100, it will be padded on the left with spaces to make seven characters. Zeros will be added to the right of the decimal point if necessary to pad it to three decimal places.

Consider this Java 1.4 code fragment that prints a three-column table of the angles between 0 and 360 degrees in degrees, radians, and grads, using onlyprintln():

for (double degrees = 0.0; degrees <>
double radians = Math.PI * degrees / 180.0;
double grads = 400 * degrees / 360;
System.out.println(degrees + " " + radians + " " + grads);
}

Its output looks like this (not very pretty):

0.0 0.0 0.0
1.0 0.017453292519943295 1.1111111111111112
2.0 0.03490658503988659 2.2222222222222223
3.0 0.05235987755982988 3.3333333333333335
...

In Java 5,printf()can easily format each number exactly five characters wide with one digit after the decimal point:

for (double degrees = 0.0; degrees <>
double radians = Math.PI * degrees / 180.0;
double grads = 400 * degrees / 360;
System.out.printf("%5.1f %5.1f %5.1f\n", degrees , radians, grads);
}

Here’s the start of the output:

0.0 0.0 0.0
1.0 0.0 1.1
2.0 0.0 2.2
3.0 0.1 3.3
...

Notice how nicely everything lines up in a monospaced font? This is incredibly useful for the two dozen programmers using Java to generate reports for VT-100 terminals and letter-quality printouts on green-and-white barred computer paper. (Those readers who haven’t written any software like that since 1984, and certainly those readers who weren’t even born in 1984, should now see why I’m less than thrilled with the addition of this 1970s technology to a 21st-century language.)

Of course, programmers printing text in proportional-width fonts, GUI table components, HTML reports, XML documents styled with XSL stylesheets, and any other output format produced since 1992 may be less enamored of this style of programming. Anyway, Java has it now. You don’t have to use it (or read the rest of this chapter) if you don’t need it.


Java Print Streams - Formatter

:
In fact, printf() is a little more general than System.out (though that’s its primary justification). Besides printf(), the PrintStreamclass also has aformat()method:

public PrintStream format(String format, Object... args)

This does exactly the same thing asprintf(). That is, the previous example could be rewritten like this and produce identical output:

for (double degrees = 0.0; degrees <>
double radians = Math.PI * degrees / 180.0;
double grads = 400 * degrees / 360;
System.out.format("%5.1f %5.1f %5.1f\n", degrees , radians, grads);
}

Why two methods, then? Theformat()method is used not just byPrintStreambut also by thejava.util.Formatterclass:

public class Formatter implements Flushable, Closeable

printf()is there solely to make C programmers feel nostalgic.

Formatteris the object-oriented equivalent ofsprintf()andfprintf()in C. Rather than writing its output onto the console, it writes it into a string, a file, or an output stream. Pass the object you want to write into to theFormatterconstructor. For example, this code fragment creates aFormatterthat writes data into a file named angles.txt:

Formatter formatter = new Formatter("angles.txt");

Once you’ve created aFormatterobject, you can write to it using theformat()method just as you would withSystem.out.format(), except that the output goes into the file rather than onto the console:

for (double degrees = 0.0; degrees <>
double radians = Math.PI * degrees / 180.0;
double grads = 400 * degrees / 360;
formatter.format("%5.1f %5.1f %5.1f\n", degrees , radians, grads);
}

Formatters are not output streams, but they can and should be flushed and closed just the same:

formatter.flush();
formatter.close();

1 comment: