数据验证是编写任何用户界面时经常要处理的一项杂务。java? 语言的正则表达式支持可以使数据验证变得更容易。您可以定义一个正则表达式,用于描述有效数据,然后让 java 运行时检查它是否匹配。但是有些类型的数据在不同地区有不同的格式。而 resourcebundle 类让您可以以一种优雅的方式使用特定于地区的数据。本文展示如何结合这两种技术来解决常见的数据输入问题。
本文讨论将正则表达式与 java resourcebundle 相结合的一种数据验证技术。java 语言对正则表达式的支持可以大大简化数据验证。您可以将数据与正则表达式进行比较,如果它们匹配,则知道数据是有效的。另一方面,java resourcebundle 包含翻译好的字符串,用于匹配用户机器上的当前语言和国家设置。resourcebundle 中的字符串通常是出现在应用程序中的文本,但是也可以是特定于某个地区的任何东西。
您将实践一个示例应用程序,该应用程序从 resourcebundles 获得正则表达式,并将它们用于数据验证。通过这种方法,就可以用一块代码来验证很多不同类型的数据。更妙的是,随着更多 resourcebundle 的添加,还可以验证更多类型的数据,并且不用更改这段代码中的任何一行。
本文的示例应用程序是在 eclipse 中用 visual editor 构建的。visual editor 是一种用于构建图形化界面的开放源码工具。为了构建自己的应用程序,您需要在计算机上安装 eclipse 和 visual editor 包。这个示例应用程序只是举例说明了验证数据的一种技巧,所以这种方法可用于任何 java 应用程序。
示例应用程序
我不想花太多的时间讨论这个示例应用程序的所有细节,我只关注其中的数据验证方面的技巧。这个应用程序验证输入到输入域中的邮政编码。您可能知道,在世界的不同地方,邮政编码千差万别。有的是数字,有的则包含字母。即使同是由数字组成的邮政编码,在不同地方其长度也不尽相同。有的国家以特定的模式排列字母和数字,而另外一些国家则采用更自由的格式。所有这些格式都可以用正则表达式来描述。例如,在美国邮政编码是一个五位数,后面还可能跟有一个破折号加一个四位数。清单 1 展示了描述这种格式的正则表达式:
清单 1. 用于美国邮政编码的正则表达式
[0-9]{5}(-[0-9]{4})? |
除了格式不同外,邮政编码并不总是被称为邮政编码。例如,美国将邮政编码称为 zip code。resourcebundle 的一种常见用法就是处理这种类型的与地区有关的差异。用于美国的 resourcebundle 可能包含短语 "enter your zip code",而在用于加拿大的 resourcebundle 中,相应的短语可能是 "enter your postal code"。我在本文中演示的技巧也是从 resourcebundle 获得用于有效邮政编码的正则表达式。
为了使这个示例简单化,您将创建一个只有一个输入域和一个 validate 按钮的 swing 应用程序。用户在输入域中输入文本,然后单击该按钮。如果数据与当前的正则表达式匹配,则应用程序显示一条消息,表明邮政编码有效。因为应用程序使用不同的 resourcebundle,所以正则表达式随着有效数据的规则的变化而变化。由于正则表达式是从文本文件中装载的一个字符串,所以当添加对新类型的邮政编码的支持时,不需要更改代码。
您将在 eclipse 中使用 eclipse visual editor 和 eclipse java development tool 的一些特性来构建这个应用程序。您可以在几乎所有开发环境中使用这种技巧。这里的代码应该可以在任何基于 eclipse 的产品中运行,例如 rational application developer。
图 1 展示了该应用程序在 eclipse visual editor 中的样子:
图 1. eclipse visual editor 中的示例应用程序

visual editor 提供了四种查看应用程序的方式。在屏幕的顶端是应用程序的可视化图像,源代码在底端。eclipse 还提供了两个视图 ―― properties 视图和 java beans 视图 ―― 可以通过这两个视图来处理应用程序。所有这些查看应用程序的方式都是由 eclipse modeling framework (emf) 控制的。由于已经有一些关于 emf 的完整书籍,所以我不会再谈更多的细节。从程序员的角度来看,重要的一点是,任何视图中的变化都会自动发送到其他视图。例如,如果您使用 properties 视图将一个对象的背景颜色设为绿色,那么可视化图像和源代码也会自动更新。
运行初始的示例应用程序
首先来看一个已经创建好的应用程序。图 2 展示了这个应用程序的运行界面:
图 2. 输入有效数据时的示例应用程序

在图 2 中,用户输入了有效的数据,并单击了 validate 按钮。如果数据无效,那么将出现图 3 所示的界面:
图 3. 输入无效数据时的示例应用程序

清单 2 展示了如何使用 清单 1 中的正则表达式来验证数据:
清单 2. 使用正则表达式
pattern pc = pattern.compile("[0-9]{5}(-[0-9]{4})?"); matcher m = pc.matcher(postalcode.gettext()); if (m.matches()) { validlabel.settext("your postal code is valid."); validlabel.setforeground(color.blue); } else { validlabel.settext("your postal code is not valid."); validlabel.setforeground(color.red); } |
清单 2 中的两条反馈消息通常会被翻译成其他语言。您还将通过使用这里展示的技巧来 “翻译” 正则表达式。与一般的翻译不同,将正则表达式转换成国际化版本是数据格式专家的工作,而不是语言专家的工作。
具体化字符串
eclipse 为代码的国际化提供了一个方便的特性。首先单击 source > externalize strings...,如图 4 所示:
图 4. externalize strings... 主菜单

eclipse 查看 java 代码,以发现应该放入到 resourcebundle 中的字符串。您将看到类似图 5 所示的对话框:
图 5. externalize strings 对话框

在图 5 中列出的所有字符串中,对话框顶部的空白字符串不需要翻译。(反馈消息的初始值是一个空白字符串。)取消对第一个字符串的选择,然后单击 next 和 finish。eclipse 创建一个新的名为 com.ibm.developerworks.messages 的类,这个类从 messages.properties 文件获取字符串。
处理国际化代码
具体化代码之后,eclipse 修改初始的类,将字符串移入 messages.properties 文件,并创建一个名为 messages 的新类。messages 类有一个名为 getstring() 的静态方法,应用程序将使用该方法来获得字符串的值。
messages 类在内部使用 resourcebundle。清单 3 展示了生成的用于创建 resourcebundle 的代码:
清单 3. 创建 resourcebundle
public class messages { private static final string bundle_name = "com.ibm.developerworks.messages"; //$non-nls-1$ private static final resourcebundle resource_bundle = resourcebundle.getbundle(bundle_name); |
稍后我将更详细地谈到如何创建 resourcebundle。
所有字符串的值都在 messages.properties 文件中,如清单 4 所示:
清单 4. messages.properties 文件
localizedvalidator.1=news gothic localizedvalidator.2=validate localizedvalidator.3=[0-9]{5}(-[0-9]{4})? localizedvalidator.4=your postal code is valid. localizedvalidator.5=your postal code is not valid. localizedvalidator.6=exit localizedvalidator.7=localized data validator localizedvalidator.8=enter your postal code, then click validate: |
从技术上说,该文件是 com/ibm/developerworks/messages.properties,但是您不必关心这个细节。生成的代码可以正确无误地找到该文件。
使用 resourcebundle 来验证数据
当使用 eclipse externalize strings 功能创建 .properties 文件时,它修改了应用程序,以便同时获取正则表达式和程序中所有其他可翻译的文本,如清单 5 所示:
清单 5. 通过 resourcebundle 使用正则表达式
pattern pc = pattern.compile(messages.getstring("localizedvalidator.3")); //$non-nls-1$ matcher m = pc.matcher(postalcode.gettext()); if (m.matches()) { validlabel.settext(messages.getstring("localizedvalidator.4")); //$non-nls-1$ validlabel.setforeground(color.blue); } else { validlabel.settext(messages.getstring("localizedvalidator.5")); //$non-nls-1$ validlabel.setforeground(color.red); } |
注意,pattern.compile() 方法使用 messages.getstring() 方法来获得正则表达式的值。当需要验证数据时,代码首先获得字符串 localizedvalidator.3,然后使用它来验证邮政编码。反馈消息也是从 properties 文件获得的。
如何装载 .properties 文件
至此,主应用程序已经可以使用 “翻译” 好的正则表达式了。所有字符串的值都来自 messages.properties 文件,那么,如何装载这些字符串的不同版本呢?答案取决于 resourcebundle 是如何创建的。
无论何时运行一个 java 程序,它都有一个特定的地区。地区由两个字母的语言代码和两个字母的国家代码来指定,这些代码是由 iso 标准定义的。地区代码还有一个不常用的变种部分,用于更精确地指定特定的地区。下面是一些例子:
- en_us 是 u.s. english 地区。
- en_ca 是 canadian english 地区。
- fr_ca 是 french canadian 地区。
- en 是 english 地区。
- en_us_unix 是 u.s. english 地区的 unix 变种。至于该变种的意义及其用法,是由应用程序的编写者定义的。
- messages_en_us.properties
- messages_en.properties
- messages.properties
记住,创建 resourcebundle 的代码指定了文件名 messages.properties。该文件名不会随着地区的改变而改变,这意味着您的代码也不需要做出更改。您只需指定这个文件名,java 运行时可以自动得出应该装载哪个特定于地区的文件。
特定于地区的 .properties 文件
一个特定于地区的 .properties 文件只包含不同于更通用的 .properties 文件的字符串。例如,如果 messages_en.properties 文件包含 localizedvalidator.9=what is your favorite color? 这一行,那么 messages_en_gb.properties 文件可能包含 localizedvalidator.9=what is your favourite colour?。如果只有这个英国化的字符串是 en_gb 地区所特有的,那么 messages_en_gb.properties 文件只需包含这个字符串。当代码向 resourcebundle 请求任何其他字符串时,如果 messages_en.properties 中有这样的字符串,就使用其中的字符串。如果 messages_en.properties 文件中没有那样的字符串,则使用 messages.properties 中的版本。如果这一系列的 .properties 文件中都没有被请求的字符串,就会抛出 java.util.missingresourceexception 异常。
清单 6 展示了美国地区的 .properties 文件所特有的一些行:
清单 6. en_us(美国)地区特有的值
localizedvalidator.4=your zip code is valid. localizedvalidator.5=your zip code is not valid. localizedvalidator.8=enter your zip code, then click validate: |
这里惟一的变化是使用 "zip code" 代替 "postal code"。可用默认的正则表达式验证该数据。
英国的邮政编码有六种不同的格式,还有一个特殊的值 gir 0aa,如清单 7 所示。(为了便于阅读,清单 7 中的正则表达式被分成两行,实际上只有一行。)
清单 7. en_gb(英国)地区特有的值
localizedvalidator.3= [a-z]([0-9]|[0-9]{2}|[a-z][0-9]|[a-z][0-9]{2}|[0-9][a-z] |[a-z][0-9][a-z]) [0-9][a-z]{2}|gir 0aa localizedvalidator.8=enter your postcode, then click validate: |
用于澳大利亚的正则表达式包括州或地区的简称,需要的空格(一个或两个),以及一个四位数,如清单 8 所示:
清单 8. en_au(澳大利亚)地区特有的值
localizedvalidator.3=(act|nsw|nt|qld|sa|tas|vic|wa)( | )[0-9]{4} localizedvalidator.8=enter your australian postal code, then click validate: |
加拿大的邮政编码格式是字母、数字、字母、一个空格、数字、字母、数字,如清单 9 所示:
清单 9. en_ca(加拿大)地区特有的值
localizedvalidator.3=[a-z][0-9][a-z] [0-9][a-z][0-9] localizedvalidator.8=enter your canadian postal code, then click validate: |
清单 10 展示了德国地区特有的值。德国的邮政编码是一个五位数:
清单 10. de(德国)地区特有的值
localizedvalidator.2=validieren localizedvalidator.3=[0-9]{5} localizedvalidator.4=ihre postleitzahl ist gültig localizedvalidator.5=ihre postleitzahl ist ungültig! localizedvalidator.6=beenden localizedvalidator.7=nls datenvalidatung localizedvalidator.8=geben sie ihre postleitzahl ein |
在运行时设置地区
现在您已经定义了 .properties 文件,接下来应该用两种方法中的一种来测试这些文件。第一种方法是在运行应用程序的时候设置 user.language 和 user.country 这两个系统属性。在 eclipse 环境中,可以右键单击一个类名,然后选择 run... 菜单,如图 6 所示:
图 6. run... 菜单

在 run 对话框中,可以设置 java vm 选项,以改变默认的语言和地区,如图 7 所示:
图 7. 在 run 对话框中设置 java vm 参数

-d 选项用于定义系统属性。您可以在命令行中使用相同的语法,例如:
java -duser.language=en -duser.country=au com.ibm.developerworks.localizedvalidator |
第二种方法是在应用程序中设置地区。通过 locale.setdefault() 方法可以在代码中设置默认的地区。清单 11 展示了如何改变 localizedvalidator 类的 main() 方法:
清单 11. 在应用程序中设置默认的地区
public static void main(string[] args) { string language = ""; string country = ""; if (args.length > 0) language = args[0]; if (args.length > 1) country = args[1]; locale.setdefault(new locale(language, country)); localizedvalidator nv = new localizedvalidator(); nv.show(); } |
如果在命令行没有指定参数,则使用用户计算机的默认地区。如果没有命令行参数,则代码 new locale("", "") 只是创建默认的地区。
也可以在 run 对话框中设置命令行参数,如图 8 所示:
图 8. 在 run 对话框中设置命令行参数

图 9 展示了指定了参数 en au 的情况下应用程序的界面:
图 9. 用于澳大利亚地区(en_au)的示例应用程序

用参数 de 运行示例应用程序时,将得到如图 10 所示的界面:
图 10. 用于德国地区(de)的示例应用程序

结束语
本文展示了如何将正则表达式与 java 语言的国际化支持相结合来验证不同类型的本地化数据。通过这种技巧,您可以支持新的数据类型,而不用更改任何代码。例如示例应用程序,如果您想添加对波兰的邮政编码的支持,那么只需创建一个 messages_pl.properties 文件。这样就在没有更改任何代码的情况下添加了对新数据类型的支持。(如果您想知道的话,那么告诉您,用于波兰的邮政编码的正则表达式是 [0-9]{2}-?[0-9]{3}。)
示例应用程序原封不动地使用 eclipse 生成的 messages 类。这个类能满足这个例子的要求,但是,应用程序启动时会装载 resourcebundle,并且直到下次运行应用程序时才能重新装载 resourcebundle。如果您想更改代码,以便动态地改变 resourcebundle,那么需要修改 messages 类,使它的字段和方法不是静态的。这做起来不难,但是您还需要修改和维护 messages.java 文件。就把这个任务作为练习吧。
还应该认识到,swing 提供了 javax.swing.jformattedtextfield 类。利用这个类可以为文本域定义一个掩码。例如,您可以使用掩码 (###) ###-####,使用户只能在文本域中输入有效的美国的电话号码。您可以使用与这里相同的技巧来从一个本地化的 resourcebundle 中获得掩码字符串。
jformattedtextfield 类有明显的优势,因为它可以在用户输入时验证数据,为用户提供直接的反馈。但是掩码字符串不如正则表达式那么灵活。例如,您可以为美国的 zip code 编写掩码 ##### 或 #####-####,但是不能同时使用这两个掩码。如果一个掩码字符串足以处理一组本地化数据类型,那么从 resourcebundle 获得掩码字符串就是本技巧的一个很好的用途。
闽公网安备 35060202000074号