Formatting with the java.text Package

The java.text package includes, among other things, a set of classes designed for generating and parsing string representations of objects. In this section, we’ll talk about three classes: NumberFormat, ChoiceFormat, and MessageFormat. Chapter 11 describes the DateFormat class. As we said earlier, the classes of the java.text package overlap to a large degree with the capabilities of the Scanner and printf-style Formatter. Despite these new features, a number of areas in the parsing of currencies, dates, and times can only be handled with the java.text package.

The NumberFormat class can be used to format and parse currency, percentages, or plain old numbers. NumberFormat is an abstract class, but it has several useful factory methods that produce formatters for different types of numbers. For example, to format or parse currency strings, use getCurrencyInstance() :

    double salary = 1234.56;
    String here =     // $1,234.56
        NumberFormat.getCurrencyInstance().format(salary);
    String italy =    // L 1.234,56
        NumberFormat.getCurrencyInstance(Locale.ITALY).format(salary);

The first statement generates an American salary, with a dollar sign, a comma to separate thousands, and a period as a decimal point. The second statement presents the same string in Italian, with a lire sign, a period to separate thousands, and a comma as a decimal point. Remember that NumberFormat worries about format only; it doesn’t attempt to do currency conversion. We can go the other way and parse a formatted value using the parse() method, as we’ll see in the next example.

Likewise, getPercentInstance() returns a formatter you can use for generating and parsing percentages. If you do not specify a Locale when calling a getInstance() method, the default Locale is used:

    double progress = 0.44;
    NumberFormat pf = NumberFormat.getPercentInstance();
    System.out.println( pf.format(progress) );    // "44%"
    try {
        System.out.println( pf.parse("77.2%") );  // "0.772"
    }
    catch (ParseException e) {}

And if you just want to generate and parse plain old numbers, use a NumberFormat returned by getInstance() or its equivalent, getNumberInstance() :

    NumberFormat guiseppe = NumberFormat.getInstance(Locale.ITALY);

    // defaults to Locale.US
    NumberFormat joe = NumberFormat.getInstance();

    try {
      double theValue = guiseppe.parse("34.663,252").doubleValue();
      System.out.println(joe.format(theValue));  // "34,663.252"
    }
    catch (ParseException e) {}

We use guiseppe to parse a number in Italian format (periods separate thousands, comma is the decimal point). The return type of parse() is Number, so we use the doubleValue() method to retrieve the value of the Number as a double. Then we use joe to format the number correctly for the default (U.S.) locale.

Here’s a list of the factory methods for text formatters in the java.text package. Again, we’ll look at the DateFormat methods in the next chapter.

    NumberFormat.getCurrencyInstance()
    NumberFormat.getCurrencyInstance(Locale inLocale)
    NumberFormat.getInstance()
    NumberFormat.getInstance(Locale inLocale)
    NumberFormat.getNumberInstance()
    NumberFormat.getNumberInstance(Locale inLocale)
    NumberFormat.getPercentInstance()
    NumberFormat.getPercentInstance(Locale inLocale)

    DateFormat.getDateInstance()
    DateFormat.getDateInstance(int style)
    DateFormat.getDateInstance(int style, Locale aLocale)
    DateFormat.getDateTimeInstance()
    DateFormat.getDateTimeInstance(int dateStyle, int timeStyle)
    DateFormat.getDateTimeInstance(int dateStyle, int timeStyle, Locale aLocale)
    DateFormat.getInstance()
    DateFormat.getTimeInstance()
    DateFormat.getTimeInstance(int style)
    DateFormat.getTimeInstance(int style, Locale aLocale)

Thus far, we’ve seen how to format numbers as text. Now, we’ll take a look at a class, ChoiceFormat, that maps numerical ranges to text. ChoiceFormat is constructed by specifying the numerical ranges and the strings that correspond to them. One constructor accepts an array of doubles and an array of Strings, where each string corresponds to the range running from the matching number up to (but not including) the next number in the array:

    double[] limits = new double [] {0, 20, 40};
    String[] labels = new String [] {"young", "less young", "old"};
    ChoiceFormat cf = new ChoiceFormat(limits, labels);
    System.out.println(cf.format(12)); //"young"
    System.out.println(cf.format(26)); // "less young"

You can specify both the limits and the labels using a special string in an alternative ChoiceFormat constructor:

    ChoiceFormat cf = new ChoiceFormat("0#young|20#less young|40#old");
    System.out.println(cf.format(40)); // old
    System.out.println(cf.format(50)); // old

The limit and value pairs are separated by vertical bars (|); the number sign (#) separates each limit from its corresponding value.

ChoiceFormat is most useful for handling pluralization in messages, enabling you to avoid hideous constructions such as, “you have one file(s) open.” You can create readable error messages by using ChoiceFormat along with the MessageFormat class.

MessageFormat

MessageFormat is a string formatter that uses a pattern string in the same way that printf() formatting does. MessageFormat has largely been replaced by printf(), which has more options and is more widely used outside of Java. Nonetheless, some may still prefer MessageFormat’s style, which is a bit less cryptic than that of printf(). MessageFormat has a static formatting method, MessageFormat.format(), paralleling the print-style formatting of String.format().

Arguments in a MessageFormat format string are delineated by curly brackets and may include information about how they should be formatted. Each argument consists of a number, an optional type, and an optional style, as summarized in Table 10-8.

Table 10-8. MessageFormat arguments

Type

Styles

Choice

pattern

Date

short, medium, long, full, pattern

Number

integer, percent, currency, pattern

Time

short, medium, long, full, pattern

Let’s use an example to clarify this:

    //Equivalent to String.format("You have %s messages.", "no");
    MessageFormat.format("You have {0} messages.", "no");

The special incantation {0} means “use element zero of the arguments supplied to the format() method.” When we generate a message by calling format(), we pass in values to replace the placeholders ({0}, {1}, ... ) in the template. In this case, we pass the string “no” as arguments[0], yielding the result, You have no messages.

Let’s try this example again, but this time, we’ll format a number and a date instead of a string argument:

    MessageFormat mf = new MessageFormat(
        "You have {0, number, integer} messages on {1, date, long}.");
        // "You have 93 messages on April 10, 2002."

    System.out.println( mf.format( 93, new Date() ) );

In this example, we need to fill in two spaces in the template, so we need two arguments. The first must be a number and is formatted as an integer. The second must be a Date and is printed in the long format.

This is still sloppy. What if there is only one message? To make this grammatically correct, we can embed a ChoiceFormat-style pattern string in our MessageFormat pattern string:

    MessageFormat mf = new MessageFormat(
      "You have {0, number, integer} message{0, choice, 0#s|1#|2#s}.");
    // "You have 1 message."
    System.out.println( mf.format( 1 ) );

In this case, we use the first argument twice: once to supply the number of messages and once to provide input to the ChoiceFormat pattern. The pattern says to add an s if the argument has the value 0 or is 2 or more.

When writing internationalized programs, you can use resource bundles to supply not only the text of messages, but also the format strings for your MessageFormat objects. In this way, you can automatically format messages that are in the appropriate language with dates and other language-dependent fields handled appropriately and in the appropriate order. Because arguments in the format string are numbered, you can refer to them in any location. For example, in English, you might say, “Disk C has 123 files”; in some other language, you might say, “123 files are on Disk C.” You could implement both messages with the same set of arguments:

    MessageFormat m1 = new MessageFormat(
        "Disk {0} has {1, number, integer} files.");
    MessageFormat m2 = new MessageFormat(
        "{1, number, integer} files are on disk {0}.");

In real life, the code could be more compact; you’d use only a single MessageFormat object, initialized with a string taken from a resource bundle. Or you’d likely want to use the static format method or switch to printf() entirely.

Get Learning Java, 4th Edition now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.