Array 类是少数不是最终类的核心类之一,也就是说您可以创建自己的 Array 子类。本部分提供了创建 Array 子类的示例,并讨论了在创建子类过程中会出现的一些问题。
如前所述,ActionScript 中的数组是不能指定数据类型的,但您可以创建只接受具有指定数据类型的元素的 Array 子类。以下部分中的示例定义名为 TypedArray 的 Array 子类,该子类中的元素限定为具有第一个参数中指定的数据类型的值。这里的 TypedArray 类仅用于说明如何扩展 Array 类,并不一定适用于生产,这有以下若干原因。第一,类型检查是在运行时而非编译时进行的。第二,当 TypedArray 方法遇到不匹配时,将忽略不匹配并且不会引发异常;当然,修改此方法使其引发异常也是很简单的。第三,此类无法防止使用数组访问运算符将任一类型的值插入到数组中。第四,其编码风格倾向于简洁而非性能优化。
注: 可以使用此处介绍的方法创建指定类型的数组。但是,更好的方法是使用 Vector 对象。Vector 实例是真正的指定类型的数组,在性能和其他一些方面比 Array 类或任何子类都好。本文所进行的讨论旨在演示如何创建 Array 子类。
声明子类
可以使用 extends 关键字来指示类为 Array 的子类。与 Array 类一样,Array 的子类应使用 dynamic 属性。否则,子类将无法正常发挥作用。
以下代码显示 TypedArray 类的定义,该类包含一个保存数据类型的常量、一个构造函数方法和四个能够将元素添加到数组的方法。此示例省略了各方法的代码,但在以后的部分中将列出这些代码并详加说明:
public dynamic class TypedArray extends Array
{
private const dataType:Class;
public function TypedArray(...args) {}
AS3 override function concat(...args):Array {}
AS3 override function push(...args):uint {}
AS3 override function splice(...args) {}
AS3 override function unshift(...args):uint {}
}
由于本示例中假定将编译器选项 -as3 设置为 true,而将编译器选项 -es 设置为 false,因而这四个被覆盖的方法均使用 AS3 命名空间而非 public 属性。这些是 Adobe Flash Builder 和 Adobe Flash Professional 的默认设置。
如果您是倾向于使用原型继承的高级开发人员,您可能会在以下两个方面对 TypedArray 类进行较小的改动,以使其在编译器选项
-es 设置为
true 的情况下进行编译。一方面,删除出现的所有
override 属性,并使用
public 属性替换 AS3 命名空间。另一方面,使用
Array.prototype 替换出现的所有四处
super。
TypedArray 构造函数
由于此子类构造函数必须接受一列任意长度的参数,从而造成了一种有趣的挑战。该挑战就是如何将参数传递给超类构造函数以创建数组。如果将一列参数作为数组进行传递,则超类构造函数会将其视为 Array 类型的一个参数,并且生成的数组长度始终只有 1 个元素。传递参数列表的传统处理方式是使用 Function.apply() 方法,此方法将参数数组作为第二个参数,但在执行函数时将其转换为一列参数。遗憾的是,Function.apply() 方法不能和构造函数一起使用。
剩下的唯一方法是在 TypedArray 构造函数中重新创建 Array 构造函数的逻辑。以下代码说明在 Array 类构造函数中使用的算法,您可以在 Array 子类构造函数中重复使用此算法:
public dynamic class Array
{
public function Array(...args)
{
var n:uint = args.length
if (n == 1 && (args[0] is Number))
{
var dlen:Number = args[0];
var ulen:uint = dlen;
if (ulen != dlen)
{
throw new RangeError("Array index is not a 32-bit unsigned integer ("+dlen+")");
}
length = ulen;
}
else
{
length = n;
for (var i:int=0; i < n; i++)
{
this[i] = args[i]
}
}
}
}
TypedArray 构造函数与 Array 构造函数中的大部分代码都相同,只在四个地方对代码进行了改动。其一,参数列表中新增了一个必需的 Class 类型参数,使用此参数可以指定数组的数据类型。其二,将传递给构造函数的数据类型分配给 dataType 变量。其三,在 else 语句中,在 for 循环之后为 length 属性赋值,以使 length 只包括相应类型的参数。其四,for 循环的主体使用 push() 方法的被覆盖版本,以便仅将正确数据类型的参数添加到数组中。以下示例显示 TypedArray 构造函数:
public dynamic class TypedArray extends Array
{
private var dataType:Class;
public function TypedArray(typeParam:Class, ...args)
{
dataType = typeParam;
var n:uint = args.length
if (n == 1 && (args[0] is Number))
{
var dlen:Number = args[0];
var ulen:uint = dlen
if (ulen != dlen)
{
throw new RangeError("Array index is not a 32-bit unsigned integer ("+dlen+")")
}
length = ulen;
}
else
{
for (var i:int=0; i < n; i++)
{
// type check done in push()
this.push(args[i])
}
length = this.length;
}
}
}
TypedArray 覆盖的方法
TypedArray 类覆盖前述四种能够将元素添加到数组的方法。在每种情况下,被覆盖方法均添加类型检查,这种检查可以防止添加不正确数据类型的元素。然后,每种方法均调用其自身的超类版本。
push() 方法使用 for..in 循环来遍历参数列表,并对每个参数执行类型检查。可以使用 splice() 方法,从 args 数组中删除所有不是正确类型的参数。在 for..in 循环结束后,args 数组仅包含 dataType 类型的值。然后对更新后的 args 数组调用 push() 的超类版本,如以下代码所示:
AS3 override function push(...args):uint
{
for (var i:* in args)
{
if (!(args[i] is dataType))
{
args.splice(i,1);
}
}
return (super.push.apply(this, args));
}
concat() 方法创建一个名为 passArgs 的临时 TypedArray 来存储通过类型检查的参数。这样,便可以重复使用 push() 方法中的类型检查代码。for..in 循环用于遍历 args 数组,并为每个参数调用 push()。由于将 passArgs 指定为类型 TypedArray,因而将执行 push() 的 TypedArray 版本。然后,concat() 方法将调用其自身的超类版本,如以下代码所示:
AS3 override function concat(...args):Array
{
var passArgs:TypedArray = new TypedArray(dataType);
for (var i:* in args)
{
// type check done in push()
passArgs.push(args[i]);
}
return (super.concat.apply(this, passArgs));
}
splice() 方法使用任意一列参数,但前两个参数始终引用索引号和要删除的元素个数。这就是为什么被覆盖的 splice() 方法仅对索引位置 2 或其以后的 args 数组元素执行类型检查。该代码中一个有趣的地方是,for 循环中的 splice() 调用看上去似乎是递归调用,但实际上并不是递归调用,这是由于 args 的类型是 Array 而非 TypedArray,也就是说,args.splice() 调用是对此方法超类版本的调用。在 for..in 循环结束后,args 数组中将只包含索引位置 2 或其以后位置中具有正确类型的值,并且 splice() 会调用其自身的超类版本,如下面的代码所示:
AS3 override function splice(...args):*
{
if (args.length > 2)
{
for (var i:int=2; i< args.length; i++)
{
if (!(args[i] is dataType))
{
args.splice(i,1);
}
}
}
return (super.splice.apply(this, args));
}
用于将元素添加到数组开头的 unshift() 方法也可以接受任意一列参数。被覆盖的 unshift() 方法使用的算法与 push() 方法使用的算法非常类似,如下面的示例代码所示:
AS3 override function unshift(...args):uint
{
for (var i:* in args)
{
if (!(args[i] is dataType))
{
args.splice(i,1);
}
}
return (super.unshift.apply(this, args));
}
}