将列表数据提供给 applet
看过关于 applet 参数化的上一篇技巧的读者可能已经注意到:我们的方法没有对一类重要的对象进行初始化。在本文中,我们将研究如果利用类反射机制对一维数组和二位数组进行初始化。我知道只有实现对更高维数组的处理才能使狂热的科学家满意,但我将把那项工作作为练习留给您。
在我的上一篇技巧中,只能处理基本类型的数组和字符串数组。考虑到任何对象最终都能由基本数据类型和字符串构建而来,所以这将不会构成多大的限制。当然,很容易将我们的技术加以扩展,之后就能直接对其他类型的数据进行初始化。
数组是用来存储列表数据的理想数据结构。我们的技术使得向 applet 传递列表参数变得很简单。 通常利用动态生成 html 文档的程序(如 servlet 或 cgi 脚本)将列表数据传递给 applet。作为示例,我们设想一个比赛记分板 applet。html 生成器将会将当前的记分板数据库输出到 param 标记中,接着相应的数组将被完全初始化 -- 这要归功于我们的参数提取方法。
列表数据项的语法
我们要实现的就是一个从 param 标记中提取一维或是二维数组的方法。一维数组的语法是:
param name="myarray" value="element1 element2 ... elementn"
各元素之间的定界符是空格。
二位数组的语法是:
param name="mymatrix" value="element11 element12 element13 |
element21 element22 element23 |
element31 element32 element33"
各行之间的定界符是 | 符号。这里,mymatrix 是一个 (3 x 3) 数组。
注意:java 支持不规则数组。 不规则数组就是各行的长度不同的数组。例如,html 作者可能会按以下方式输入帕斯卡三角形:
param name="pascaltriangle" value=" 1 |
1 1 |
1 2 1 |
1 3 3 1 |
1 4 6 4 1 |
1 5 10 10 5 1 |
1 6 15 20 15 6 1"
初始化完成之后,pascaltriangle 域的内容将是:
pascaltriangle[0] = {1}
pascaltriangle[1] = {1, 1}
pascaltriangle[2] = {1, 2, 1}
pascaltriangle[3] = {1, 3, 3, 1}
pascaltriangle[4] = {1, 4, 6, 4, 1}
pascaltriangle[5] = {1, 5, 10, 10, 5, 1}
pascaltriangle[5] = {1, 6, 15, 20, 15, 6, 1}
通常,程序员应该只声明 pascaltriangle,而不进行内存分配。我们的提取方法负责分配内存。但让我们假定已为第四行分配了内存,如下所示:
pascaltriangle[3] = new int[2];
我们的方法将只提取前两个元素。这样,第四行的初始化结果将是:
pascaltriangle[3] = {1, 3}
数组知识回顾
正如您在以上代码清单中看到的那样,我们的方法实现有点“深奥”。因此,在研究源代码之前回顾有关数组的几点知识是个不错的主意。
我们都对 java 的类型层次结构比较熟悉:java 有一组预定义的基本数据类型(int、float...),还有 object 的子类的一个继承树,object 类是所有类的最终超类。但 java 中还存在一个不很出名的平行层次结构,我称其为数组层次结构。您无论何时在类型层次结构中定义了一个新类型 foo,您实际上也同时定义了一个自动结合到数组层次结构中的新类型 foo[]。数组层次结构中的每个类(基本数据类型的数组除外)都是 object[] 的子类。容易引起混淆的是:object[] 和基本数据类型的数组都是 object 的子类。图 1 表明了这一点。

令人感到奇怪的是,java 根本就没有多维数组,只有一维数组。多维数组实际上是“一维数组的数组的数组的数组...”。因此,我们可以创建不规则数组。事实上,我们甚至可以不对某些行进行初始化,而将它们保留为空值。
数组提取方法的实现
现在我们可以查看源代码了。正如您所见,其中加了大量注释。通常,包含如此多的注释不是个好习惯,但在这里,我们要将已经抽象的 java 数组包装在由类反射机制提供的元数据抽象层中。结果,多数程序语句都不能表明其自身的含义,所以在这种情况下对几乎每个代码行作注释是无可非议的。
无论何时对一维或是二维数组进行初始化,最终我们都需要用 html 作者输入的行对一维数组进行初始化。我们设计了一个方法来完成这一操作:
/**
* 用符号处理器 (tokenizer) 的内容填充一维数组。
* 符号被转换为数组的内容类型。
*
* @param array 要填充的数组。
* @param elementtokens 包含要填入数组的符号的符号处理器。
*/
private static void fillonedimensionalarray(object array,
stringtokenizer elementtokens)
throws illegalaccessexception {
if (array != null && elementtokens != null && array.getclass().isarray()) { // 双重检验。
// 数组应该容纳哪种类型的元素?
class componenttype = array.getclass().getcomponenttype();
int numelements = elementtokens.counttokens();
// 为数组元素赋值。
//
// 请注意,我们确保索引不会超出范围。可能未给数组分配组足够的空间,
// 以致无法容纳分析后的全部元素。
for (int j = 0; j < array.getlength(array) && j < numelements; j++) {
// 将符号转换为数组所容纳的类型。
// 然后将其添加到数组中。
if (componenttype.equals(boolean.class))
array.setboolean(array, j, boolean.valueof(elementtokens.nexttoken().trim()).booleanvalue());
else if (componenttype.equals(byte.class))
array.setbyte(array, j, byte.valueof(elementtokens.nexttoken().trim()).bytevalue());
else if (componenttype.equals(char.class))
array.setchar(array, j, elementtokens.nexttoken().charat(0));
else if (componenttype.equals(double.class))
array.setdouble(array, j, double.valueof(elementtokens.nexttoken().trim()).doublevalue());
else if (componenttype.equals(float.class))
array.setfloat(array, j, float.valueof(elementtokens.nexttoken().trim()).floatvalue());
else if (componenttype.equals(int.class))
array.setint(array, j, integer.valueof(elementtokens.nexttoken().trim()).intvalue());
else if (componenttype.equals(long.class))
array.setlong(array, j, long.valueof(elementtokens.nexttoken().trim()).longvalue());
else if (componenttype.equals(short.class))
array.setshort(array, j, short.valueof(elementtokens.nexttoken().trim()).shortvalue());
else if (componenttype.equals(string.class))
array.set(array, j, elementtokens.nexttoken());
}
}
}
我们使用 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());
}
/***************
闽公网安备 35060202000074号