java.text 包允許通過與特定語言無關的方式格式化文本消息、日期和數值。許多人配合 MessageFormat 類使用資源包來為用戶本地化消息。更多的人似乎使用 DateFormat 和 SimpleDateFormat 類來操作日期字符串,既用於輸入也用於輸出。最少見的用法似乎是使用 NumberFormat 類及其相關的子類 DecimalFormat 和 ChoiceFormat 。在本月的討論中,我們將研究一下這三個未得到充分利用的類以及 Currency 類,看看 J2SE 1.4 已經變得有多麼的全球化。
數值格式化基類:NumberFormat
如果您來自美國,您會在較大的數值中間放置逗號來表示千和百萬(等等,每三個數值使用一個逗號)。對於浮點數,您將在整數部分和小數部分之間放置小數點。對於金錢,貨幣符號 $ 放在金額的前面。如果 您從來沒有到過美國以外的地方,可能就不會關心用元(¥)來格式化的日本貨幣,用英鎊(£)來格式化的英國貨幣,或者用歐元(€)來表示的其他歐洲國家的貨幣。
對於那些我們確實關心的貨幣,我們可以使用 NumberFormat 及其相關的類來格式化它們。開發人員使用 NumberFormat 類來讀取用戶輸入的數值,並格式化將要顯示給用戶看的輸出。
與 DateFormat 類似, NumberFormat 是一個抽象類。您永遠不會創建它的實例――相反,您總是使用它的子類。雖然可以通過子類的構造函數直接創建子類,不過 NumberFormat 類提供了一系列 get XXXInstance() 方法,用以獲得不同類型的數值類的特定地區版本。這樣的方法共有五個:
getCurrencyInstance()
getInstance()
getIntegerInstance()
getNumberInstance()
getPercentInstance()
具體使用哪一個方法取決於您想要顯示的數值類型(或者想要接受的輸入類型)。每個方法都提供了兩個版本――一個版本適用於當前地區,另一個版本接受一個 Locale作為參數,以便可能地指定一個不同的地區。
在 J2SE 1.4中, NumberFormat 新增的內容是 getIntegerInstance()、 getCurrency() 和 setCurrency() 方法。下面讓我們研究一下新的 getIntegerInstance() 方法。稍後將會探討 get/set 貨幣方法。
使用 NumberFormat 的基本過程是獲得一個實例並使用該實例。挑選恰當的實例的確需要費一番思量 。通常您不希望使用通用的 getInstance 或者 getNumberInstance() 版本 ,因為您不確切知道您將會得到什麼。相反 ,您會使用像 getIntegerInstance() 這樣的方法 ,因為您希望把某些內容顯示為整數而不需要任何小數值 。清單1展示了這一點 ,我們在其中把數值 54321 顯示為適合於美國和德國的格式。
清單 1. 使用 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));
}
}
運行該代碼將產生如清單2所示的輸出。注意第一種格式(美國)中的逗號分隔符和第二種格式中的點號分隔符。
清單 2. NumberFormat 輸出
54,321
54.321
學習如何迭代 DecimalFormat 中的字符
雖然 NumberFormat 是一個抽象類,並且您將通過像 getIntegerInstance() 這樣的各種方法來使用它的實例,但是 DecimalFormat 類提供了該類的一個具體版本。 您可以顯式地指定字符模式,用以確定如何顯示正數、負數、小數和指數。如果不喜歡用於不同地區的預定義格式,您可以創建自己的格式。(在內部,或許 NumberFormat 使用的就是 DecimalFormat 。)基本的 DecimalFormat 功能在 J2SE 平台的 1.4 版中並沒有改變。改變之處在於添加了 formatToCharacterIterator()、 getCurrency() 和 setCurrency() 方法。
我們將快速浏覽一下新的 formatToCharacterIterator 方法及其關聯的 NumberFormat.Field 類。J2SE 1.4 引入了 CharacterIterator 的概念,它允許雙向地遍歷文本。對於 formatToCharacterIterator ,您將獲得它的子接口 AttributedCharacterIterator ,這個子接口允許您找出關於該文本的信息。對於 DecimalFormat 的情況 ,那些屬性是來自 NumberFormat.Field 的鍵 。通過使用 AttributedCharacterIterator , 您完全可以根據所產生的結果構造自己的字符串輸出 。清單3使用了一個百分數實例來提供一個簡單的演示:
清單 3. 使用 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);
}
}
}
}
清單4顯示了程序的輸出(顯示在一小段消息之後,以使其更易於閱讀)。基本上, formatToCharacterIterator() 方法的工作方式與調用 format() 相同,只不過前者除了格式化輸出字符串外,還要使用屬性來標記輸出中的每個字符(例如,位於位置 X 處的字符是否為一個整數?)。將 50.25 顯示為百分數,在美國地區的輸出為“5,025%”。通過檢查輸出 ,除“%”外的每個字符都是整數,包括冒號 。除了數值之外 ,逗號也被標記為一個分組分隔符,百分號被標記為一個百分數。每個數字的屬性都是一個 java.util.Map ,其中每個屬性被顯示為 key=value (鍵=值)的形式。在存在多個屬性的情況下,屬性列表中的屬性之間用逗號分隔。
清單 4. formatToCharacterIterator() 輸出
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)}
基於值范圍和 ChoiceFormat 確定消息
ChoiceFormat 是 NumberFormat 的另一個具體子類。它的定義和行為在 J2SE 1.4 中沒有改變。 ChoiceFormat 並不會真正幫助您格式化數值,但它的確允許您自定義與某個值關聯的文本。在最簡單的情況下,我們可以設想一下顯示出錯消息的情況。如果存在導致失敗的單個原因,您希望使用單詞“is”。如果有兩個或者多個原因,您希望使用單詞“are”。如清單5所示, ChoiceFormat 允許您把一系列的值映射為不同的文本字符串。
ChoiceFormat 類通常與 MessageFormat 類一起使用,以產生與語言無關的拼接起來的消息。這裡沒有說明的是如何使用 ResourceBundle (它通常與 ChoiceFormat 一起使用)來獲得那些字符串。關於如何使用資源包的信息 ,請參見 參考資料;特別地,“Java 國際化基礎”教程提供了關於這方面的深入討論 。
清單 5. 使用 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));
}
}
}
執行該程序將產生如清單6所示的輸出:
清單 6. ChoiceFormat 輸出
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.
使用 Currency 進行貨幣計算
前面提到過的 getCurrency() 和 setCurrency() 方法返回新的 java.util.Currency 類的一個實例。這個類允許訪問不同國家的 ISO 4217 貨幣代碼。雖然自從 getCurrencyInstance() 引入以來您就能配合 NumberFormat 一起使用它,然而除了它們的數字顯示外,您永遠不能獲得或顯示某個地區的貨幣符號。有了 Currency 類,現在很容易就可以做到這一點。
正如前面提到過的 ,貨幣代碼來自ISO 4217。通過傳入某個國家的 Locale 或者貨幣的實際字母代碼, Currency.getInstance() 將返回一個有效的 Currency 對象。 NumberFormat 的 getCurrency() 方法將在創建特定地區的貨幣實例之後做同樣的事情。 清單7顯示了如何獲得貨幣實例,以及如何格式化將要顯示為貨幣的數值。記住這些轉換僅用於顯示。如果需要在貨幣之間轉換金額,應該在確定如何顯示值之前進行轉換。
清單 7. 使用 getCurrencyInstance() 和 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(); } }
遺憾的是,為歐元或者英鎊返回的貨幣符號不是實際的符號,而是三位的貨幣代碼(來自 ISO 4217)。然而在使用 getCurrencyInstance() 的情況下,實際的符號將會顯示出來,如圖1所示。
圖 1. 看見實際的貨幣符號
結束語
對於軟件全球化來說,所需做的不僅僅是自定義文本消息。雖然把文本消息轉移到資源包中至少完成了工作的一半,但是也不要忘了處理與地區密切相關的數值和貨幣顯示。並不是每個人都像在美國一樣使用冒號和點號來進行數字顯示,每個人都必須處理自己的貨幣細節。雖然我們不必依賴像 $$$.99 這樣的老式 COBOL 圖形字符串,但是通過使用特定於地區的 NumberFormat 實例, 您可以使自己的程序更加國際化。