applet的参数化--对数组进行初始化(2)

发表于:2007-07-14来源:作者:点击数: 标签:
我们使用 Class.getComponentType() 方法获取给定数组对象所容纳的元素类型。一旦我们获得这些信息,我们就知道应将行元素转换为何种类型。这是在一个循环语句中完成的。 您可能已猜到了,Array.setByte(Object obj, int i, byte datum) 用字节变量 datum 为
我们使用 Class.getComponentType() 方法获取给定数组对象所容纳的元素类型。一旦我们获得这些信息,我们就知道应将行元素转换为何种类型。这是在一个循环语句中完成的。

您可能已猜到了,Array.setByte(Object obj, int i, byte datum) 用字节变量 datum 为 obj 数组的第 i 个元素赋值。这相当于 ((byte[])obj)[i] = datum。

下面开始分析实现的核心部分。我对 Util.initializeApplet(Applet, String) 方法(在“Java 技巧 57”中实现)进行了扩展,在其中添加了一个条件语句,这个条件语句高速缓存数组域并对它们进行初始化。

import java.applet.*;
import java.lang.reflect.*;
import java.util.*;

public abstract class Util {

/**
* 对 applet 的名称以给定筛选前缀开头的非 final 公共域进行初始化。
* 初始值将从 HTML PARAM 标记中读取。
* *
* @param applet 要初始化的 applet。
* @param filterPrefix 只对那些以此前缀开头的域进行初始化。
*
* 如果前缀为空值,将对所有非 final 公共域进行初始化。
*/
public static void initializeApplet(Applet applet, String filterPrefix) {

Class metaclass = applet.getClass();
Field[] fields = metaclass.getFields();
String param = null;

for (int i = 0; i < fields.length; i++) {
try {
param = applet.getParameter(fields[i].getName());

if (param == null ||
Modifier.isFinal(fields[i].getModifiers()) ||
((filterPrefix != null) &&
!fields[i].getName().startsWith(filterPrefix))
)
continue;

Class fieldType = fields[i].getType();

if (fieldType.equals(boolean.class)) {
fields[i].setBoolean(applet, Boolean.valueOf(param).booleanValue());
}
else if (fieldType.equals(byte.class)) {
fields[i].setByte(applet, Byte.valueOf(param).byteValue());
}

/*********************************************

* 具体细节已被删除。请参阅上一篇文章。

* 要获得完整的代码,请下载源文件。

*********************************************/

// 对数组进行初始化。
else if (fieldType.isArray()) {

// 此处我们知道正在处理的域是一个数组。
// 但数组要容纳何种类型的元素呢?
Class componentType = fieldType.getComponentType();

// 对一维数组进行初始化。
if (componentType.isPrimitive() ||
componentType.equals(String.class)) {

// 用 StringTokenizer 分析由 HTML 作者提供的数组元素。

StringTokenizer elementTokens = new StringTokenizer(param);
int numElements = elementTokens.countTokens();

// 请注意,fields[i] 只是表示数组对象的元数据域。
// 我们需要一个数组引用,以便为它的元素赋值。
//
// 这样:
Object array = fields[i].get(applet);

// 如因某种原因已构造了数组,
// 则使它保持原样。

// 否则:以适当的类型构造数组,
// 并为其分配足够的内存空间,
// 以容纳由 HTML 作者提供全部元素。
if (array == null) {
// 构造类型为 componentType 的数组,
// 并为 numElements 分配空间。
fields[i].set(applet, Array.newInstance(componentType, numElements));

// 获取刚构造的数组的引用。
array = fields[i].get(applet);
}

// 用包含在 elementTokens 中的元素填充数组。
fillOneDimensionalArray(array, elementTokens);
}

// 对二维数组进行初始化。
else if (componentType.isArray() &&
(componentType.getComponentType().isPrimitive() ||
componentType.getComponentType().equals(String.class))) {

// 子数组(即各行)由 "|" 符号分隔。
// 使用这种定界符将二维表分解成一系列表示一维数组的
// 符号。我们称之为子数组。
StringTokenizer subarrayTokens = new StringTokenizer(param, "|");
int numSubarrays = subarrayTokens.countTokens();

// 请注意,"fields[i]" 只是表示数组对象的元数据域。
// 我们需要此数组的一个引用,以便为它的元素赋值。
//
// 这样:
Object array = fields[i].get(applet);

// 如因某种原因已构造了数组,
// 则使它保持原样。
//
// 否则:以适当的类型构造数组,
// 并为其分配足够的内存空间,
// 以容纳 HTML 作者提供的全部行。
//
// 请注意,此处的变量 "array" 必定是数组的数组。
// 因此“适当的类型”必定是数组类型。
if (array == null) {
// 构造类型为 componentType 的数组,
// 并为 numSubarrays 行分配内存空间。
fields[i].set(applet, Array.newInstance(componentType, numSubarrays));

// 获取刚构造的数组的数组的引用。
array = fields[i].get(applet);
}

// 依次对每个子数组进行初始化。

// 请注意,我们确保索引不超出范围。
// 可能是为数组分配的空间不足,
// 以致无法容纳 HTML 作者提供的所有行。
for (int j = 0; j < Array.getLength(array) && j < numSubarrays; j++) {
// 用 StringTokenizer 分析由 HTML 作者提供的行元素。
StringTokenizer elementTokens = new StringTokenizer(subarrayTokens.nextToken());
int numElements = elementTokens.countTokens();

// 引入此新变量的唯一目的就是使代码更易于阅读。
// 但稍后我们就会看到这引起了敏感的争论。 Object subArray = ((Object[])array)[j];

// 如因某种原因已构造了子数组,
// 则使它保持原样。
//
// 否则:以适当的类型构造子数组,并为其分配充足的内存空间,
// 以便容纳由 HTML 作者提供的全部元素。

if (subArray == null) {
// "componentType" 是 "array" 的类型,因此子数组的类型 "componentType.getComponentType()" 的数组即为 "subArray" 的类型。

subArray = Array.newInstance(componentType.getComponentType(), numElements);
}

// 下面的语句是必要的。在前面的条件语句中,
// 可能已为 "subarray" 分配了一个不同于
// "((Object[]array)[j]" 的对象引用。
// 这样就为这两个变量重新分配的别名。
// 这样就确保应用于子数组的全部变化
// 将在子数组的数组(即 "array")中得到反映。


((Object[])array)[j] = subArray;

// 用 elementTokens 中包含的元素填充子数组。
fillOneDimensionalArray(subArray, elementTokens);
}
}
}
}

catch (Exception e) {
System.err.println(e + " while initializing " + fields[i]);
}
}
}
}


当 type 为 {void.class, boolean.class, byte.class, short.class, int.class, long.class, float.class, double.class} 之一时,type.isPrimitive() 方法即返回 "true"。

分配数组的类反射方法如下所示:

bazField.set(fooObject, Array.newInstance(componentType, numElements));


这将创建数组并为其分配存储空间,然后用该数组为由变量 bazField 表示且属于变量 fooObject 的域赋值。换句话说,fooObject 类的 FooClass 有一个由 bazField 表示的属性,同时我们用这个数组为 FooClass 的 fooObject 实例中的相应域赋值。

以下语句的语法和语义都可能令您吃惊。

Object subArray = ((Object[])array)[j];


该语句的目的是从名为 array 的二维数组中提取第 j 行。我们将该行存储在变量 subArray 中。这个变量的类型 Object,因为我们预先不知道要处理的一维数组为何种类型;回忆一下前面关于数组的讨论,任何类型的一维数组的最近公共超类即 Object 类。若不是另有原因,该变量数组的类型也是 Object。我们预先不知道要处理的是基本类型的一维数组,还是其他类型的数组,同样,最近的公共超类仍是 Object。当我们看到这条语句时,我们已经知道了这样一个事实:变量 array 表示一个二维数组;回忆一下我们关于数组的讨论,二维数组实际上是一维数组的一维数组。因此,它们实际上都是 Object[] 的子类。这就是我们能将变量 array 转换为 Object[] 的原因所在。接下来的工作就很简单了,提取第 j 行作为 Object 实例,并将其赋给变量 subArray。

小结
本技巧说明了如何利用类反射机制来减轻程序员开发可配置 applet 的负担。本月的这篇技巧用数组提取例程对我们的技术作了补充。通过使用一个完成所有参数提取工作的方法,您就免除了不得不一再输入 getParameter 的乏味工作。


作者简介

yvon.sauvageau Yvon 获得了 McGill 大学(位于加拿大蒙特利尔市)的数学及计算机科学学位。他有七年的编程经验,范围涉及商业服务器应用程序到 GUI 编程。两年前他迷上了 Java(没办法!)。他去年一月份通过了 Sun 认证 Java 程序员考试,最近又通过了 Sun 认证 Java 开发员考试。他目前是巴黎 MTLI-NSK 技术公司的一名咨询人员。他目前承担的项目是一个人力资源管理系统,该系统完全用 Java 编写,并且基于 ObjectStore OODBMS。在欧洲的生活很有乐趣,但作为加拿大人他很想念曲棍球!

参考资源

请参阅我的上一篇技巧,"Java Tip 57: Applet parameterization via class reflection"
http://www.javaworld.com/javatips/jw-javatip57.html
有关类反射的信息,请参阅 Chuck McManis 在 JavaWorld 发表的文章,"Take an in-depth look at the Java Reflection API"
http://www.javaworld.com/javaworld/jw-09-1997/jw-09-indepth.html

原文转自:http://www.ltesting.net