格式化数值和货币

来源:互联网 发布:教绘画软件下载 编辑:程序博客网 时间:2024/04/28 10:12

格式化数值和货币


John Zukowski, 总裁, JZ Ventures, Inc


2003 年 9 月 29 日

应用程序国际化要求将文本消息和所显示的数值格式化为用户所在地区的相应语言和样式。有了J2SE 平台的 Merlin 版本,格式化整数变得简单 起来,现在您可以找出 ISO 4217 货币代码。在本月的“Merlin的魔力”中,JohnZukowski 将向您展示如何格式化数值,并介绍用于发现本地货币代码的新特性。欢迎参与本文的 论坛,与作者和其他读者共享您对本文的想法。(也可以单击文章顶部或者底部的 讨论来访问论坛。)

java.text 包允许通过与特定语言无关的方式格式化文本消息、日期和数值。许多人配合 MessageFormat 类使用资源包来为用户本地化消息。更多的人似乎使用 DateFormat SimpleDateFormat 类来操作日期字符串,既用于输入也用于输出。最少见的用法似乎是使用 NumberFormat 类及其相关的子类 DecimalFormat ChoiceFormat 。在本月的讨论中,我们将研究一下这三个未得到充分利用的类以及 Currency 类,看看 J2SE 1.4 已经变得有多么的全球化。

数值格式化基类:NumberFormat

如果您来自美国,您会在较大的数值中间放置逗号来表示千和百万(等等,每三个数值使用一个逗号)。对于浮点数,您将在整数部分和小数部分之间放置小数点。对于金钱,货币符号 $ 放在金额的前面。如果 您从来没有到过美国以外的地方,可能就不会关心用元(¥)来格式化的日本货币,用英镑(£)来格式化的英国货币,或者用欧元(?)来表示的其他欧洲国家的货币。

对于那些我们确实关心的货币,我们可以使用 NumberFormat 及其相关的类来格式化它们。开发人员使用 NumberFormat 类来读取用户输入的数值,并格式化将要显示给用户看的输出。

DateFormat 类似, NumberFormat 是一个抽象类。您永远不会创建它的实例――相反,您总是使用它的子类。虽然可以通过子类的构造函数直接创建子类,不过 NumberFormat 类提供了一系列 get XXXInstance() 方法,用以获得不同类型的数值类的特定地区版本。这样的方法共有五个:

“您说西红柿,”……

“地区”代表事物特定于语言或者国家的表示形式,通常是指文本消息和数值格式。例如,在美国,人们使用单词“color”,而在英国,人们使用单词“colour”。 java.util.Locale 类就是用来表示不同地区的。

对于使用Java语言进行国际化的优秀入门读物来说,再没有比Joe Sam Shirah所著的“ Java国际化基础”教程更好的了,该教程非常详细地介绍了I18N。

  • 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. 看见实际的货币符号
Seeing the Currency Symbols




结束语

对于软件全球化来说,所需做的不仅仅是自定义文本消息。虽然把文本消息转移到资源包中至少完成了工作的一半,但是也不要忘了处理与地区密切相关的数值和货币显示。并不是每个人都像在美国一样使用冒号和点号来进行数字显示,每个人都必须处理自己的货币细节。虽然我们不必依赖像 $$$.99 这样的老式 COBOL 图形字符串,但是通过使用特定于地区的 NumberFormat 实例, 您可以使自己的程序更加国际化。


原创粉丝点击