The java.text package allows you to format text messages, dates, and numbers in a manner independent of a specific language. Many people use resource bundles with the MessageFormat class to localize messages for users. Even more people seem to use the DateFormat and SimpleDateFormat classes for working with date strings, both for input and output. The least used of the bunch seems to be the NumberFormat class and its associated subclasses DecimalFormat and ChoiceFormat. This month, we'll take a look at these three underused classes, plus the Currency class, to see just how worldly J2SE 1.4 has become.
The root of it all: NumberFormat
If you're from the United States, you place a comma in large numbers to designate thousands and millions (and so on up for every three digits). For floating-point numbers, you place a decimal as the separator between the whole part of the number and the fraction. With money, the currency symbol is $, placed before the amount. If you never travel outside the States or never expect your programs to for that matter, you might not care about formatting currencies in yens (¥) for Japan, pounds (£) for Great Britain, or euros (€) for most of the rest of Europe.
For those who do care, there's NumberFormat and its related classes. Developers use the NumberFormat class to read numbers entered by users and format output to be displayed for users to see.
Similar to DateFormat, the NumberFormat class is abstract. You never create an instance of it -- instead, you always work with a subclass. While you can create a subclass directly through the constructor for the subclass, the NumberFormat class offers a series of getXXXInstance() methods to get locale-specific versions for different types of number classes. There are five such methods available:
getCurrencyInstance()getInstance()getIntegerInstance()getNumberInstance()getPercentInstance()
Which one you use depends on what type of number you want to display (or accept for input). Each method offers two versions -- one that works for the current locale and one that accepts a Locale as an argument to possibly specify a different locale.
New to J2SE 1.4 with NumberFormat are the getIntegerInstance(), getCurrency(), and setCurrency() methods. Let's see how to use NumberFormat by examining the new getIntegerInstance() method. We'll explore the get/set currency methods later.
The basic process of using NumberFormat is to get an instance and use it. Picking the right instance does require some thought. Typically, you don't want to use the general getInstance() or getNumberInstance() versions because you don't know exactly what you are going to get. Instead, you'd use something like getIntegerInstance() because you know you want to display something as an integer, without any decimal amount. This is demonstrated in Listing 1, where we display the number 54321 in formats appropriate for the United States and Germany:
Listing 1. Working with NumberFormat
import java.text.*;
import java.util.*;
public class IntegerSample {
public static void main(String args[]) {
int amount = 54321;
NumberFormat usFormat =
NumberFormat.getIntegerInstance(Locale.US);
System.out.println(usFormat.format(amount));
NumberFormat germanFormat =
NumberFormat.getIntegerInstance(Locale.GERMANY);
System.out.println(germanFormat.format(amount));
}
}
|
Running the code produces the output in Listing 2. Notice the comma for the first (U.S.) format and the period separator for the second (German) formatted number.
Listing 2. NumberFormat output
54,321 54.321 |
Learning to iterate through the characters in DecimalFormat
While NumberFormat is abstract and you work with instances of it through various methods like getIntegerInstance(), the DecimalFormat class offers a concrete version of that class. You can explicitly specify the character pattern for how you want positive, negative, fractional, and exponential numbers to appear. If you don't like the predefined formats for the different locales, you can create your own. (Under the covers, DecimalFormat is probably what NumberFormat is using.) The basic DecimalFormat functionality hasn't changed with the 1.4. release of the J2SE platform. What has changed is the addition of the formatToCharacterIterator(),
getCurrency(), and setCurrency() methods.
We'll take a quick look at the new formatToCharacterIterator method and its associated NumberFormat.Field class. J2SE 1.4 introduces the concept of a CharacterIterator, which allows you to traverse bi-directionally through text. With formatToCharacterIterator, you get its subinterface AttributedCharacterIterator, which allows you to find out information about that text. In the case of DecimalFormat, those attributes are keys from NumberFormat.Field. By using AttributedCharacterIterator, you literally can build up your own string output from the generated results. Listing 3 uses a percent instance to show you a simple demonstration:
Listing 3. Working with formatToCharacterIterator()
import java.text.*;
import java.util.*;
public class DecimalSample {
public static void main(String args[]) {
double amount = 50.25;
NumberFormat usFormat = NumberFormat.getPercentInstance(Locale.US);
if (usFormat instanceof DecimalFormat) {
DecimalFormat decFormat = (DecimalFormat)usFormat;
AttributedCharacterIterator iter =
decFormat.formatToCharacterIterator(new Double(amount));
for (char c = iter.first();
c != CharacterIterator.DONE;
c = iter.next()) {
// Get map for current character
Map map = iter.getAttributes();
// Display its attributes
System.out.println("Char: " + c + " / " + map);
}
}
}
}
|
Listing 4 shows the program's output (after a little massaging to make it more readable). Basically, the formatToCharacterIterator() method works the same way as calling format(), but in addition to formatting the output string, it tags each character in the output with its attributes (for instance, is the character at position X an integer?). The output of displaying 50.25 as a percentage is "5,025%" for the U.S. locale. By examining the output, everything but the "%" is an "integer," including the comma. Besides the numbers, the comma is also flagged as a grouping separator and the percent sign is flagged as a percent. The attributes of each digit are a java.util.Map, in which each attribute is shown as key=value, where both are the same. In the case of a comma where multiple attributes are present, the list is comma separated.
Listing 4. formatToCharacterIterator() output
Char: 5 / {java.text.NumberFormat$Field(integer)=
java.text.NumberFormat$Field(integer)}
Char: , / {java.text.NumberFormat$Field(grouping separator)=
java.text.NumberFormat$Field(grouping separator),
java.text.NumberFormat$Field(integer)=
java.text.NumberFormat$Field(integer)}
Char: 0 / {java.text.NumberFormat$Field(integer)=
java.text.NumberFormat$Field(integer)}
Char: 2 / {java.text.NumberFormat$Field(integer)=
java.text.NumberFormat$Field(integer)}
Char: 5 / {java.text.NumberFormat$Field(integer)=
java.text.NumberFormat$Field(integer)}
Char: % / {java.text.NumberFormat$Field(percent)=
java.text.NumberFormat$Field(percent)}
|
Deciding on messages based on a value range and ChoiceFormat
ChoiceFormat is another of those concrete subclasses of NumberFormat. Its definition and behavior haven't changed with the 1.4 release. ChoiceFormat doesn't really help you format numbers, but it does allow you to customize the text associated with a value. In the simplest case, imagine displaying an error message. If there is a single reason for failure, you want to use the word "is." If there are two or more reasons, you want to use the word "are." As Listing 5 illustrates, ChoiceFormat allows you to map a range of values to different text strings.
The ChoiceFormat class is typically used with the MessageFormat class to produce concatenated messages in a language-neutral format. Not shown here is the use of a ResourceBundle (which is typically used with ChoiceFormat) to get those strings. See Resources for information on how to work with resource bundles; specifically, the "Java internationalization basics" tutorial provides an in-depth discussion.
Listing 5. Working with ChoiceFormat
import java.text.*;
import java.util.*;
public class ChoiceSample {
public static void main(String args[]) {
double limits[] = {0, 1, 2};
String messages[] = {
"is no content",
"is one item",
"are many items"};
ChoiceFormat formats = new ChoiceFormat(limits, messages);
MessageFormat message = new MessageFormat("There {0}.");
message.setFormats(new Format[]{formats});
for (int i=0; i<5; i++) {
Object formatArgs[] = {new Integer(i)};
System.out.println(i + ": " + message.format(formatArgs));
}
}
}
|
Executing the program produces the output shown in Listing 6:
Listing 6. ChoiceFormat output
0: There is no content. 1: There is one item. 2: There are many items. 3: There are many items. 4: There are many items. |
Counting our money with Currency
The previously mentioned getCurrency() and setCurrency() methods return an instance of the new java.util.Currency class. This class provides access to the ISO 4217 currency codes for various countries. While you've been able to use getCurrencyInstance() with NumberFormat since it was introduced, you could never get or display the different currency symbols for a locale apart from their numeric display. With the new Currency class, you easily can.
As previously mentioned, currency codes come from ISO 4217. By passing in either the Locale for a country or the actual alphabetic code for the currency, Currency.getInstance() will give you back a valid Currency object. The getCurrency() method of NumberFormat will do the same after creating a currency instance for a specific locale. Listing 7 shows how to get a currency instance and how to format a number to be displayed as currency. Remember that these conversions are for display only. If you need to convert amounts between currencies, do so before figuring out how to display the values.
Listing 7. Working with getCurrencyInstance() and Currency
import java.text.*;
import java.util.*;
import java.awt.*;
import javax.swing.*;
public class CurrencySample {
public static void main(String args[]) {
StringBuffer buffer = new StringBuffer(100);
Currency dollars = Currency.getInstance("USD");
Currency pounds = Currency.getInstance(Locale.UK);
buffer.append("Dollars: ");
buffer.append(dollars.getSymbol());
buffer.append("\n");
buffer.append("Pound Sterling: ");
buffer.append(pounds.getSymbol());
buffer.append("\n-----\n");
double amount = 5000.25;
NumberFormat usFormat = NumberFormat.getCurrencyInstance(Locale.US);
buffer.append("Symbol: ");
buffer.append(usFormat.getCurrency().getSymbol());
buffer.append("\n");
buffer.append(usFormat.format(amount));
buffer.append("\n");
NumberFormat germanFormat =
NumberFormat.getCurrencyInstance(Locale.GERMANY);
buffer.append("Symbol: ");
buffer.append(germanFormat.getCurrency().getSymbol());
buffer.append("\n");
buffer.append(germanFormat.format(amount));
JFrame frame = new JFrame("Currency");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JTextArea ta = new JTextArea(buffer.toString());
JScrollPane pane = new JScrollPane(ta);
frame.getContentPane().add(pane, BorderLayout.CENTER);
frame.setSize(200, 200);
frame.show();
}
}
|
Unfortunately, the currency symbols returned for things like euros and pounds are not the actual symbols, but are instead the three-digit currency code (from ISO 4217). However, with getCurrencyInstance(), the symbol is displayed, as shown in Figure 1.
Figure 1. Seeing the currency symbols
There is more to globalizing your programs than just customizing the text messages. While moving text messages into resource bundles is at least half the work, don't forget about dealing with locale-centric numbers and currency displays, too. Not everyone uses commas and periods in the same way as in the United States for numeric display, and everyone has to deal with their own currency specifics. While we don't have to rely on the old COBOL picture strings like $$$.99, by using locale-specific NumberFormat instances, you can make your programs more internationalized.
| Name | Size | Download method |
|---|---|---|
| j-mer08133.jar | 15KB | HTTP |
Information about download methods
- Peruse John Zukowski's complete collection of Magic with Merlin tips.
- "Java internationalization basics" (developerWorks, April 2002) is an excellent tutorial on the Java programming language's support for multilingual and multicountry environments.
- Read the
NumberFormatclass Javadoc. - Read the
Localeclass Javadoc. - "Unicode and software internationalization" (developerWorks, April 2000) offers additional insight for globalizing your Java programs.
- "Internationalizing your Eclipse plug-in" (developerWorks, June 2002) explores how to write Eclipse plug-ins for the international market.
- The only internationalization-specific book for the Java platform available is
Java Internationalization by David Czarnecki and Andy Deitsch (O'Reilly & Associates, 2001), although it does not include 1.4-specific information.
- Find hundreds more Java technology resources on the
developerWorks Java technology zone.

John Zukowski conducts strategic Java consulting with JZ Ventures, Inc., offers technical support through AnswerSquad.com, and is working with SavaJe Technologies to develop a next-generation mobile phone platform. His latest books are Mastering Java 2, J2SE 1.4 (Sybex, April 2002) and Learn Java with JBuilder 6 (Apress, March 2002). Contact John at jaz@zukowski.net.
Comments (Undergoing maintenance)





