将列表数据提供给 applet 看过关于 applet 参数化的上一篇技巧的读者可能已经注意到我们的方法没有对一类重要的对象进行初始化在本文中我们将研究如果利用类反射机制对一维数组和二位数组进行初始化我知道只有实现对更高维数组的处理才能使狂热的科学家满意但我将把那项工作作为练习留给您 在我的上一篇技巧中只能处理基本类型的数组和字符串数组考虑到任何对象最终都能由基本数据类型和字符串构建而来所以这将不会构成多大的限制当然很容易将我们的技术加以扩展之后就能直接对其他类型的数据进行初始化 数组是用来存储列表数据的理想数据结构我们的技术使得向 applet 传递列表参数变得很简单 通常利用动态生成 HTML 文档的程序(如 servlet 或 CGI 脚本)将列表数据传递给 applet作为示例我们设想一个比赛记分板 appletHTML 生成器将会将当前的记分板数据库输出到 PARAM 标记中接着相应的数组将被完全初始化 这要归功于我们的参数提取方法 列表数据项的语法 我们要实现的就是一个从 PARAM 标记中提取一维或是二维数组的方法一维数组的语法是 PARAM NAME=myArray VALUE=element element elementN 各元素之间的定界符是空格 二位数组的语法是 PARAM NAME=myMatrix VALUE=element element element | element element element | element element element 各行之间的定界符是 | 符号这里myMatrix 是一个 ( x ) 数组 注意Java 支持不规则数组 不规则数组就是各行的长度不同的数组例如HTML 作者可能会按以下方式输入帕斯卡三角形 PARAM NAME=pascalTriangle VALUE= | | | | | | 初始化完成之后pascalTriangle 域的内容将是 pascalTriangle[] = {} pascalTriangle[] = { } pascalTriangle[] = { } pascalTriangle[] = { } pascalTriangle[] = { } pascalTriangle[] = { } pascalTriangle[] = { } 通常程序员应该只声明 pascalTriangle而不进行内存分配我们的提取方法负责分配内存但让我们假定已为第四行分配了内存如下所示 pascalTriangle[] = new int[]; 我们的方法将只提取前两个元素这样第四行的初始化结果将是 pascalTriangle[] = { } 数组知识回顾 正如您在以上代码清单中看到的那样我们的方法实现有点深奥因此在研究源代码之前回顾有关数组的几点知识是个不错的主意 我们都对 Java 的类型层次结构比较熟悉Java 有一组预定义的基本数据类型(intfloat)还有 Object 的子类的一个继承树Object 类是所有类的最终超类但 Java 中还存在一个不很出名的平行层次结构我称其为数组层次结构您无论何时在类型层次结构中定义了一个新类型 Foo您实际上也同时定义了一个自动结合到数组层次结构中的新类型 Foo[]数组层次结构中的每个类(基本数据类型的数组除外)都是 Object[] 的子类容易引起混淆的是Object[] 和基本数据类型的数组都是 Object 的子类图 表明了这一点 图 两个平行的层次结构令人感到奇怪的是Java 根本就没有多维数组只有一维数组多维数组实际上是一维数组的数组的数组的数组因此我们可以创建不规则数组事实上我们甚至可以不对某些行进行初始化而将它们保留为空值 数组提取方法的实现 现在我们可以查看源代码了正如您所见其中加了大量注释通常包含如此多的注释不是个好习惯但在这里我们要将已经抽象的 Java 数组包装在由类反射机制提供的元数据抽象层中结果多数程序语句都不能表明其自身的含义所以在这种情况下对几乎每个代码行作注释是无可非议的 无论何时对一维或是二维数组进行初始化最终我们都需要用 HTML 作者输入的行对一维数组进行初始化我们设计了一个方法来完成这一操作 /** * 用符号处理器 (tokenizer) 的内容填充一维数组 * 符号被转换为数组的内容类型 * * @param array 要填充的数组 * @param elementTokens 包含要填入数组的符号的符号处理器 */ private static void fillOneDimensionalArray(Object array StringTokenizer elementTokens) throws IllegalAccessException { if (array != null && elementTokens != null && arraygetClass()isArray()) { // 双重检验 // 数组应该容纳哪种类型的元素? Class componentType = arraygetClass()getComponentType(); int numElements = untTokens(); // 为数组元素赋值 // // 请注意我们确保索引不会超出范围可能未给数组分配组足够的空间 // 以致无法容纳分析后的全部元素 for (int j = ; j < ArraygetLength(array) && j < numElements; j++) { // 将符号转换为数组所容纳的类型 // 然后将其添加到数组中 if (componentTypeequals(booleanclass)) ArraysetBoolean(array j BooleanvalueOf(elementTokensnextToken()trim())booleanValue()); else if (componentTypeequals(byteclass)) ArraysetByte(array j BytevalueOf(elementTokensnextToken()trim())byteValue()); else if (componentTypeequals(charclass)) ArraysetChar(array j elementTokensnextToken()charAt()); else if (componentTypeequals(doubleclass)) ArraysetDouble(array j DoublevalueOf(elementTokensnextToken()trim())doubleValue()); else if (componentTypeequals(floatclass)) ArraysetFloat(array j FloatvalueOf(elementTokensnextToken()trim())floatValue()); else if (componentTypeequals(intclass)) ArraysetInt(array j IntegervalueOf(elementTokensnextToken()trim())intValue()); else if (componentTypeequals(longclass)) ArraysetLong(array j LongvalueOf(elementTokensnextToken()trim())longValue()); else if (componentTypeequals(shortclass)) ArraysetShort(array j ShortvalueOf(elementTokensnextToken()trim())shortValue()); else if (componentTypeequals(Stringclass)) Arrayset(array j elementTokensnextToken()); } } } 我们使用 ClassgetComponentType() 方法获取给定数组对象所容纳的元素类型一旦我们获得这些信息我们就知道应将行元素转换为何种类型这是在一个循环语句中完成的 您可能已猜到了ArraysetByte(Object obj int i byte datum) 用字节变量 datum 为 obj 数组的第 i 个元素赋值这相当于 ((byte[])obj)[i] = datum 下面开始分析实现的核心部分我对 UtilinitializeApplet(Applet String) 方法(在Java 技巧 中实现)进行了扩展在其中添加了一个条件语句这个条件语句高速缓存数组域并对它们进行初始化 import javaapplet*; import javalangreflect*; import javautil*; public abstract class Util { /** * 对 applet 的名称以给定筛选前缀开头的非 final 公共域进行初始化 * 初始值将从 HTML PARAM 标记中读取 * * * @param applet 要初始化的 applet * @param filterPrefix 只对那些以此前缀开头的域进行初始化 * * 如果前缀为空值将对所有非 final 公共域进行初始化 */ public static void initializeApplet(Applet applet String filterPrefix) { Class metaclass = appletgetClass(); Field[] fields = metaclassgetFields(); String param = null; for (int i = ; i < fieldslength; i++) { try { param = appletgetParameter(fields[i]getName()); if (param == null || ModifierisFinal(fields[i]getModifiers()) || ((filterPrefix != null) && !fields[i]getName()startsWith(filterPrefix)) ) continue; Class fieldType = fields[i]getType(); if (fieldTypeequals(booleanclass)) { fields[i]setBoolean(applet BooleanvalueOf(param)booleanValue()); } else if (fieldTypeequals(byteclass)) { fields[i]setByte(applet BytevalueOf(param)byteValue()); } /*************** |