Introduction to Printf for Java

If you're a veteran C programmer (as many Java programmers are), you have probably become familiar with the printf function. This swiss army knife of data formatting is an uncontested success story in software component reuse; it is probably the single most reused software component in all of programming history.

If you're not a veteran C programmer, however, then you probably have no clue what I'm talking about. The printf function, a small part of the well-known C library called stdio, is an extremely versatile tool for converting and formatting data into strings. Whether your program outputs numbers, characters, or strings, chances are good that printf can format it just the way you want.

I was once an enthusiastic C programmer, but it didn't take me long to betray C for Java. After my "conversion," however, I soon realized that I would not be able to survive without some essential C functions, including an easy way to format text. That was when I ported the printf function to Java. Printf for Java is one of several features available in Lava, Sharkysoft's proprietary class library.

Depending on your programming style, Printf for Java's features can be employed in many ways, giving each programmer the freedom to use printf in the manner that makes him the most comfortable. Examples of a few usage patterns are given below.

Consider the following formatted output:

(    3,   1.73,        0x9)
(    9,   3.00,       0x51)
(   27,   5.20,      0x2d9)
(   81,   9.00,     0x19a1)
(  243,  15.59,     0xe6a9)
(  729,  27.00,    0x81bf1)
( 2187,  46.77,   0x48fb79)
( 6561,  81.00,  0x290d741)
(19683, 140.30, 0x17179149)

This output is a set of triplets, listing

  1. the first nine powers of 3,
  2. the square roots of those powers, rounded to two decimal places, and
  3. the squares of those powers, in hexadecimal notation.

As you study the code examples in this article, remember that each example produces exactly the output shown above. Before reading on, try to imagine how you might do this -- that is, in Java, how would you output neatly formatted numbers, in a variety of styles, to produce a table like the one shown above? If you're up for the challenge, set this article aside and give it a shot. Then come back, and let's compare results.

Doing It Like A C Programmer

If you took my challenge -- and you probably didn't, because you're lazy, like me -- then you probably found out that with only Java's core class library, it takes a lot of work. But it doesn't have to! The printf features in Printf for Java are designed to make formatted numerical output extremely easy. Here's version 1, using Printf for Java:

for (int i = 3; i <= 20000; i *= 3)
  ( "(%5d, %6.2f, %#10x)\n",
    new Object[]
    { new Integer(i),
      new Float((float) Math.sqrt((double) i)),
      new Integer(i * i)

That's it! Can you believe it? Without Printf for Java, you would have had to write something much more intense, I am sure. In the code above, out is a method of Printf that sends printf output to the console. The first parameter to out -- that strange-looking string -- is called the format string. This format string names the items that Printf for Java will output:

1. First, a string: "("
2. Next, an int, right-aligned in a 5-character field: %5d
3. Again, another string: ""
4. Next, a float, rounded to two decimal places, right-aligned in a 6-character field: %6.2f
5. Again, a string: ""
6. Another int, in standard hexadecimal, right-aligned in a 10-character field: %#10x
7. And finally, another string: ")\n"

Observe that wherever numerical substitutions occur, there are special codes, each beginning with a '%' character and ending with a letter. These codes are called format specifiers. Whenever format specifiers are present in the format string, additional arguments must be supplied in a data vector, which are formatted and included in the output at the substitution points. The data vector is given in the second parameter as an Object array. An Object array is used because the format string may contain any number substitution flags, each calling for any type of data. Since Java does not support variable argument lists (yet), the generic Object array is the most suitable alternative.

Why Object arrays?

The C programming language allows direct (though dangerous) manipulation of pointers. Thus, in C, variable-length argument lists are possible and often used. Java, however, enforces safer memory access rules and hence cannot allow variable-length argument lists. The next best thing in Java is the variable-length Object array, so that's what Printf for Java uses.

Speeding Things Up

In the previous code example, the format string had to be interpreted before it could be used to format the data in the Object array. If you're going to print a long list of items that all follow the same format, you can speed things up by making sure the format string gets interpreted only once. This is accomplished by interpreting the format string before the loop, and then reusing the same interpretation in each iteration. The following example demonstrates how:

PrintfFormatString fmt = new PrintfFormatString ("(%5d, %6.2f, %#10x)\n");
for (int i = 3; i <= 20000; i *= 3)
  ( fmt,
    new Object[]
    { new Integer(i),
      new Float((float) Math.sqrt((double) i)),
      new Integer(i * i)

In this example, fmt, a PrintfFormatString object, is initialized before entering the loop. When fmt is initialized, the format string is "compiled" into a "formatting engine." Since this step occurs before the loop, the same engine can be used and reused in each pass through the loop, resulting in more efficient execution. If execution speed is important, you should apply this technique wherever you believe the same format string will be used over and over again. Preallocate your format strings and save them away in static variables. Why parse a format string 100 times, when just once will do?

The Easy Road

The previous two examples use Object arrays, along with primitive encapsulation classes (i.e., Integer, Float, etc.), to pass the substitution arguments. If you don't like doing it this way, there is an alternative:

for (int i = 3; i <= 20000; i *= 3)
  ( "(%5d, %6.2f, %#10x)\n",
    new PrintfData()
    .add((float) Math.sqrt((double) i))
    .add(i * i)

In this example, the PrintfData class insulates the programmer from the rigors of manually building an Object array. Note that the add method is overloaded for each type of primitive data (int, float, long, etc.). The result is an object that Printf for Java can automatically convert into the required Object array when it needs it.

This kind of parameter-passing technique is not uncommon in situations where methods require argument lists of variable size and type. However, I do not recommend using it if you can avoid it. The reason for this is that it simply is not as efficient as explicit Object array creation. The reason? Internally, the Object array must eventually be created anyway.

To make matter worse, most list-building classes, such as PrintfData, rely on a growable vector of Objects (such as java.util.Vector), since the size of the array cannot be computed at compile time. Some of the resulting inefficiency can be abated by predicting the vector's final size in the constructor. (For example, in the code shown above, the expression "new PrintfData()" ought to be replaced with "new PrintfData(3)", since it is clear from the code that add will be called only 3 times.) However, the overhead of each add call is still incurred, so the actual speed-up will be minimal.

Staying Platform-Neutral

In our running example, each line of formatted output is terminated with the newline character ('\n'). While this works perfectly on Unix machines -- and most of the time on Win32 machines as well -- it may not always work on other platforms. This is because different platforms use different line-ending characters. If you're writing cross-platform Java software, and you need your program to terminate each line of output in a manner that is appropriate for the host platform, then you should not use '\n' by itself. Instead, prefix the '\n' character with a '%' character. This two-character sequence, "%\n", indicates that the system-dependant line terminator should be substituted at run time. Thus, it would be better if the format string in the above examples was "(%5d, %6.2f, %#10x)%\n".

%\n is not a standard printf feature, but is an extension available only in Printf for Java. Because the Java programming environment is different from the C programming environment in so many different ways, you will find that Printf for Java adds many sensible, useful features to C's original printf API. Don't worry about compatibility, however; Printf for Java is backward compatible with the C's printf specification. For complete details, read the Printf for Java Specification.

I have demonstrated only small sample of the many features of Printf for Java. If you are porting an old C program to Java, or if your new Java program needs to output formatted text, then you should definitely check out Printf for Java.

Copyright ©1998-2004 Sharkysoft. All rights reserved.