数据验证是编写任何用户界面时经常要处理的一项杂务
Java? 语言的正则表达式支持可以使数据验证变得更容易
您可以定义一个正则表达式
用于描述有效数据
然后让 Java 运行时检查它是否匹配
但是有些类型的数据在不同地区有不同的格式
而 ResourceBundle 类让您可以以一种优雅的方式使用特定于地区的数据
本文展示如何结合这两种技术来解决常见的数据输入问题
本文讨论将正则表达式与 Java ResourceBundle 相结合的一种数据验证技术Java 语言对正则表达式的支持可以大大简化数据验证您可以将数据与正则表达式进行比较如果它们匹配则知道数据是有效的另一方面Java ResourceBundle 包含翻译好的字符串用于匹配用户机器上的当前语言和国家设置ResourceBundle 中的字符串通常是出现在应用程序中的文本但是也可以是特定于某个地区的任何东西
您将实践一个示例应用程序该应用程序从 ResourceBundles 获得正则表达式并将它们用于数据验证通过这种方法就可以用一块代码来验证很多不同类型的数据更妙的是随着更多 ResourceBundle 的添加还可以验证更多类型的数据并且不用更改这段代码中的任何一行
本文的示例应用程序是在 Eclipse 中用 Visual Editor 构建的Visual Editor 是一种用于构建图形化界面的开放源码工具为了构建自己的应用程序您需要在计算机上安装 Eclipse 和 Visual Editor 包这个示例应用程序只是举例说明了验证数据的一种技巧所以这种方法可用于任何 Java 应用程序
示例应用程序
我不想花太多的时间讨论这个示例应用程序的所有细节我只关注其中的数据验证方面的技巧这个应用程序验证输入到输入域中的邮政编码您可能知道在世界的不同地方邮政编码千差万别有的是数字有的则包含字母即使同是由数字组成的邮政编码在不同地方其长度也不尽相同有的国家以特定的模式排列字母和数字而另外一些国家则采用更自由的格式所有这些格式都可以用正则表达式来描述例如在美国邮政编码是一个五位数后面还可能跟有一个破折号加一个四位数清单 展示了描述这种格式的正则表达式
清单 用于美国邮政编码的正则表达式
[]{}([]{})?
除了格式不同外邮政编码并不总是被称为邮政编码例如美国将邮政编码称为 ZIP CodeResourceBundle 的一种常见用法就是处理这种类型的与地区有关的差异用于美国的 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
图 展示了该应用程序在 Eclipse Visual Editor 中的样子
图 Eclipse Visual Editor 中的示例应用程序
Visual Editor 提供了四种查看应用程序的方式在屏幕的顶端是应用程序的可视化图像源代码在底端Eclipse 还提供了两个视图 —— Properties 视图和 Java Beans 视图 —— 可以通过这两个视图来处理应用程序所有这些查看应用程序的方式都是由 Eclipse Modeling Framework (EMF) 控制的由于已经有一些关于 EMF 的完整书籍所以我不会再谈更多的细节从程序员的角度来看重要的一点是任何视图中的变化都会自动发送到其他视图例如如果您使用 Properties 视图将一个对象的背景颜色设为绿色那么可视化图像和源代码也会自动更新
运行初始的示例应用程序
首先来看一个已经创建好的应用程序图 展示了这个应用程序的运行界面
图 输入有效数据时的示例应用程序
在图 中用户输入了有效的数据并单击了 Validate 按钮如果数据无效那么将出现图 所示的界面
图 输入无效数据时的示例应用程序
清单 展示了如何使用 清单 中的正则表达式来验证数据
清单 使用正则表达式
Pattern pc = pile([]{}([]{})?); Matcher m = pcmatcher(postalCodegetText()); if (mmatches()) { validLabelsetText(Your postal code is valid); validLabelsetForeground(ColorBLUE); } else { validLabelsetText(Your postal code is not valid); validLabelsetForeground(ColorRED); }
清单 中的两条反馈消息通常会被翻译成其他语言您还将通过使用这里展示的技巧来 翻译 正则表达式与一般的翻译不同将正则表达式转换成国际化版本是数据格式专家的工作而不是语言专家的工作
具体化字符串
Eclipse 为代码的国际化提供了一个方便的特性首先单击 Source > Externalize Strings如图 所示
图 Externalize Strings 主菜单
Eclipse 查看 Java 代码以发现应该放入到 ResourceBundle 中的字符串您将看到类似图 所示的对话框
图 Externalize Strings 对话框
在图 中列出的所有字符串中对话框顶部的空白字符串不需要翻译(反馈消息的初始值是一个空白字符串)取消对第一个字符串的选择然后单击 Next 和 FinishEclipse 创建一个新的名为 comibmdeveloperworksMessages 的类这个类从 messagesproperties 文件获取字符串
处理国际化代码
具体化代码之后Eclipse 修改初始的类将字符串移入 messagesproperties 文件并创建一个名为 Messages 的新类Messages 类有一个名为 getString() 的静态方法应用程序将使用该方法来获得字符串的值
Messages 类在内部使用 ResourceBundle清单 展示了生成的用于创建 ResourceBundle 的代码
清单 创建 ResourceBundle
public class Messages { private static final String BUNDLE_NAME = comibssages; //$NONNLS$ private static final ResourceBundle RESOURCE_BUNDLE = ResourceBundlegetBundle(BUNDLE_NAME);
稍后我将更详细地谈到如何创建 ResourceBundle
所有字符串的值都在 messagesproperties 文件中如清单 所示
清单 messagesproperties 文件
LocalizedValidator=News Gothic LocalizedValidator=Validate LocalizedValidator=[]{}([]{})? LocalizedValidator=Your postal code is valid LocalizedValidator=Your postal code is not valid LocalizedValidator=Exit LocalizedValidator=Localized Data Validator LocalizedValidator=Enter your postal code then click Validate:
从技术上说该文件是 com/ibm/developerworks/messagesproperties但是您不必关心这个细节生成的代码可以正确无误地找到该文件
使用 ResourceBundle 来验证数据
当使用 Eclipse Externalize Strings 功能创建 properties 文件时它修改了应用程序以便同时获取正则表达式和程序中所有其他可翻译的文本如清单 所示
清单 通过 ResourceBundle 使用正则表达式
Pattern pc = pile(MessagesgetString(LocalizedValidator)); //$NONNLS$ Matcher m = pcmatcher(postalCodegetText()); if (mmatches()) { validLabelsetText(MessagesgetString(LocalizedValidator)); //$NONNLS$ validLabelsetForeground(ColorBLUE); } else { validLabelsetText(MessagesgetString(LocalizedValidator)); //$NONNLS$ validLabelsetForeground(ColorRED); }
注意pile() 方法使用 MessagesgetString() 方法来获得正则表达式的值当需要验证数据时代码首先获得字符串 LocalizedValidator然后使用它来验证邮政编码反馈消息也是从 properties 文件获得的
如何装载 properties 文件
至此主应用程序已经可以使用 翻译 好的正则表达式了所有字符串的值都来自 messagesproperties 文件那么如何装载这些字符串的不同版本呢?答案取决于 ResourceBundle 是如何创建的
无论何时运行一个 Java 程序它都有一个特定的地区地区由两个字母的语言代码和两个字母的国家代码来指定这些代码是由 ISO 标准定义的地区代码还有一个不常用的变种部分用于更精确地指定特定的地区下面是一些例子
en_US 是 US English 地区
en_CA 是 Canadian English 地区
fr_CA 是 French Canadian 地区
en 是 English 地区
en_US_UNIX 是 US English 地区的 UNIX 变种至于该变种的意义及其用法是由应用程序的编写者定义的
当创建一个新的 ResourceBundle 时Java 运行时根据当前的地区查找文件例如如果当前地区是 en_US那么 Java 运行时依次查找以下文件
messages_en_USproperties
messages_enproperties
messagesproperties
当 ResourceBundle 收集翻译好的字符串时在 messages_en_USproperties 中发现的任何字符串都具有比 messages_enproperties 和 messagesproperties 中具有相同名称的字符串更高的优先级如果运行时没有发现任何特定于地区的文件那么它将使用 messagesproperties 中的字符串
记住创建 ResourceBundle 的代码指定了文件名 messagesproperties该文件名不会随着地区的改变而改变这意味着您的代码也不需要做出更改您只需指定这个文件名Java 运行时可以自动得出应该装载哪个特定于地区的文件
特定于地区的 properties 文件
一个特定于地区的 properties 文件只包含不同于更通用的 properties 文件的字符串例如如果 messages_enproperties 文件包含 LocalizedValidator=What is your favorite color? 这一行那么 messages_en_GBproperties 文件可能包含 LocalizedValidator=What is your favourite colour?如果只有这个英国化的字符串是 en_GB 地区所特有的那么 messages_en_GBproperties 文件只需包含这个字符串当代码向 ResourceBundle 请求任何其他字符串时如果 messages_enproperties 中有这样的字符串就使用其中的字符串如果 messages_enproperties 文件中没有那样的字符串则使用 messagesproperties 中的版本如果这一系列的 properties 文件中都没有被请求的字符串就会抛出 javautilMissingResourceException 异常
清单 展示了美国地区的 properties 文件所特有的一些行
清单 en_US(美国)地区特有的值
LocalizedValidator=Your ZIP Code is valid LocalizedValidator=Your ZIP Code is not valid LocalizedValidator=Enter your ZIP Code then click Validate:
这里惟一的变化是使用 ZIP Code 代替 postal code可用默认的正则表达式验证该数据
英国的邮政编码有六种不同的格式还有一个特殊的值 GIR AA如清单 所示(为了便于阅读清单 中的正则表达式被分成两行实际上只有一行)
清单 en_GB(英国)地区特有的值
LocalizedValidator= [AZ]([]|[]{}|[AZ][]|[AZ][]{}|[][AZ] |[AZ][][AZ]) [][AZ]{}|GIR AA LocalizedValidator=Enter your postcode then click Validate:
用于澳大利亚的正则表达式包括州或地区的简称需要的空格(一个或两个)以及一个四位数如清单 所示
清单 en_AU(澳大利亚)地区特有的值
LocalizedValidator=(ACT|NSW|NT|QLD|SA|TAS|VIC|WA)( | )[]{} LocalizedValidator=Enter your Australian postal code then click Validate:
加拿大的邮政编码格式是字母数字字母一个空格数字字母数字如清单 所示
清单 en_CA(加拿大)地区特有的值
LocalizedValidator=[AZ][][AZ] [][AZ][] LocalizedValidator=Enter your Canadian postal code then click Validate:
清单 展示了德国地区特有的值德国的邮政编码是一个五位数
清单 de(德国)地区特有的值
LocalizedValidator=Validieren LocalizedValidator=[]{} LocalizedValidator=Ihre Postleitzahl ist gültig LocalizedValidator=Ihre Postleitzahl ist ungültig! LocalizedValidator=Beenden LocalizedValidator=NLS Datenvalidatung LocalizedValidator=Geben Sie Ihre Postleitzahl ein
在运行时设置地区
现在您已经定义了 properties 文件接下来应该用两种方法中的一种来测试这些文件第一种方法是在运行应用程序的时候设置 userlanguage 和 untry 这两个系统属性在 Eclipse 环境中可以右键单击一个类名然后选择 Run 菜单如图 所示
图 Run 菜单
在 Run 对话框中可以设置 Java VM 选项以改变默认的语言和地区如图 所示
图 在 Run 对话框中设置 Java VM 参数
D 选项用于定义系统属性您可以在命令行中使用相同的语法例如
java Duserlanguage=en untry=
AU comibmdeveloperworksLocalizedValidator
第二种方法是在应用程序中设置地区通过 LocalesetDefault() 方法可以在代码中设置默认的地区清单 展示了如何改变 LocalizedValidator 类的 main() 方法
清单 在应用程序中设置默认的地区
public static void main(String[] args) { String language = ; String country = ; if (argslength > ) language = args[]; if (argslength > ) country = args[]; LocalesetDefault(new Locale(language country)); LocalizedValidator nv = new LocalizedValidator(); nvshow(); }
如果在命令行没有指定参数则使用用户计算机的默认地区如果没有命令行参数则代码 new Locale( ) 只是创建默认的地区
也可以在 Run 对话框中设置命令行参数如图 所示
图 在 Run 对话框中设置命令行参数
图 展示了指定了参数 en AU 的情况下应用程序的界面
图 用于澳大利亚地区(en_AU)的示例应用程序
用参数 de 运行示例应用程序时将得到如图 所示的界面
图 用于德国地区(de)的示例应用程序
结束语
本文展示了如何将正则表达式与 Java 语言的国际化支持相结合来验证不同类型的本地化数据通过这种技巧您可以支持新的数据类型而不用更改任何代码例如示例应用程序如果您想添加对波兰的邮政编码的支持那么只需创建一个 messages_plproperties 文件这样就在没有更改任何代码的情况下添加了对新数据类型的支持(如果您想知道的话那么告诉您用于波兰的邮政编码的正则表达式是 []{}?[]{})
示例应用程序原封不动地使用 Eclipse 生成的 Messages 类这个类能满足这个例子的要求但是应用程序启动时会装载 ResourceBundle并且直到下次运行应用程序时才能重新装载 ResourceBundle如果您想更改代码以便动态地改变 ResourceBundle那么需要修改 Messages 类使它的字段和方法不是静态的这做起来不难但是您还需要修改和维护 Messagesjava 文件就把这个任务作为练习吧
还应该认识到Swing 提供了 javaxswingJFormattedTextField 类利用这个类可以为文本域定义一个掩码例如您可以使用掩码 (###) #######使用户只能在文本域中输入有效的美国的电话号码您可以使用与这里相同的技巧来从一个本地化的 ResourceBundle 中获得掩码字符串
JFormattedTextField 类有明显的优势因为它可以在用户输入时验证数据为用户提供直接的反馈但是掩码字符串不如正则表达式那么灵活例如您可以为美国的 ZIP Code 编写掩码 ##### 或 #########但是不能同时使用这两个掩码如果一个掩码字符串足以处理一组本地化数据类型那么从 ResourceBundle 获得掩码字符串就是本技巧的一个很好的用途