Array 類別是少數並非 final 的核心類別之一,這表示您可以建立自己的 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 命名空間而非
public
特質,因為此範例會假設編譯器選項
-as3
已設定為
true
,而且編譯器選項
-es
已設定為
false
。這些都是 Adobe Flash Builder 和 AdobeFlashProfessional 的預設設定。
若您是偏好使用原型繼承的進階開發人員,可以對 TypedArray 類別做兩個小變更,使它在編譯器選項
-es
設定為
true
的情況下進行編譯。首先移除所有的
override
特質,並將 AS3 命名空間更換為
public
特質。其次,以
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
陳述式中,
length
屬性的值是指定在
for
迴圈之後,因此
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 類別會覆寫 Array 類別的四種方法,而這四種方法都能新增元素到陣列中。在每種情況中,覆寫方法都會新增類型檢查,以防止新增資料類型不正確的元素。接著,每種方法都會呼叫其本身的父類別版本。
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));
}
}