Books / Patterns for Beginning Programmers / Chapter 28
Dynamic Formatting
It is almost impossible to get this far in an introductory programming
course without making extensive use of format specifiers (e.g., with the
printf()
method in the PrintWriter
class or the format()
method in
the String
class). However, most, if not all, of the format specifiers
you have seen and/or used have probably been hard-coded. It turns out
that there are many situations in which format specifiers must be
created while a program is running.
In this chapter
Motivation
If you want to print all of the elements of a non-negative int[]
in a
field of width 10, it’s easy to do so using the format specifier
"%10d"
as follows:
for (int i = 0; i < data.length; i++) {
System.out.printf("%10d\n", data[i]);
}
However, now suppose, instead, that you want the field to be as narrow as possible. Since you can’t know the value of the elements of the array when you are writing the program, you can’t hard-code the format specifier.
Review
Fortunately, you already have some patterns that can help you solve this
problem. First, from the discussion of accumulators in Chapter
16,
you know that you can find the largest int
in an int[]
named data
as follows:
max = -1;
for (int i = 0; i < data.length; i++) {
if (data[i] > max) max = data[i];
}
Second, from the discussion of digit counting in Chapter
13,
you know that you can find the number of digits in an int
value named
max
as follows:
width = (int) (Math.log10(max)) + 1;
So, all you need to complete the solution to the dynamic formatting problem is a format specifier.
Thinking About The Problem
Fortunately, the format specifier is a String
object, and you can
construct and manipulate String
objects while a program is running.
For example, returning to the situation in which you want to use a field
of width 10
, you could use a String
variable named fs
for the
format String
as follows:
fs = "%10d\n";
for (int i = 0; i < data.length; i++) {
System.out.printf(fs, data[i]);
}
Now, all you need to do is replace the hard-coded 10
in fs
with the
value contained in a variable.
The Pattern
In particular, what you need to do is use String
concatenation (or a
StringBuilder
object) to construct the format String
. Recall that a
format specifier has the following syntax:
%[flags][width][.precision]conversion
where:
- flags is one or more of:
-
to indicate left-justification,+
to indicate required inclusion of the sign,,
to include grouping separators, etc. - width indicates the width of the field
- precision indicates the number of digits to the right of the decimal point for real numbers
- conversion is one of
b
for aboolean
,c
for achar
,d
for an integer,f
for a real number,s
for aString
, etc.
and items in square brackets are optional.
So, assuming all of the variables have been declared, you can construct a format specifier at run-time as follows:
fs = "%";
if (flags != null) fs += flags;
if (width > 0) fs += width;
if (precision > 0) fs += "." + precision;
fs += conversion;
Examples
Suppose you want to illustrate the non-repeating nature of the digits of
\(\pi\) by printing a table in which the first line
contains one digit to the right of the decimal point, the second
contains two digits to the right of the decimal point, etc. To
accomplish this you need to construct the format specifier inside of a
loop, and print Math.PI
using that format specifier at each iteration.
This can be accomplished as follows:
for (int digits = 1; digits <= 10; digits++) {
fs = "%" + (digits + 2) + "." + digits + "f\n";
System.out.printf(fs, Math.PI);
}
Note that this example uses digits + 2
to account for the leading 3.
in the output.
As another example, suppose you want to create a String
called
result
from a String
called source
, and you want result
to
satisfy the following specifications:
- It must have
width
characters in total; and - The characters in
source
must be centered withinresult
.
You know from the discussion of centering in Chapter
26
that there must be width - source.length()
spaces in result
with
“half” of them to the left of source
and “half” of them to the right
of source
. This can be accomplished as follows:
public static String center(String source, int width) {
int field, n, append;
String fs, result;
// Calculate the number of spaces in the resulting String
n = width - source.length();
if (n <= 0) return source;
// Calculate the width of the field for source (it will be
// right-justified in this field)
field = (width + source.length()) / 2;
// Calculate the number of spaces to append to the right
append = width - field;
// Build the format specifier
fs = "%" + field + "s%" + append + "s";
result = String.format(fs, source, " ");
return result;
}
The source
will be right justified in a field that is
(width/2 - source.length())/2
characters wide and it will be followed
by a single space that will be right justified in a field that is as
wide as is necessary to fill the field.