Array クラスの拡張

Flash Player 9 以降、Adobe AIR 1.0 以降

Array クラスはコアクラスとしては珍しく final 指定されていないため、Array のサブクラスを独自に定義できます。 このセクションでは、Array のサブクラスを作成する方法について例を示し、その際に発生すると考えられる問題について説明します。

前述したとおり ActionScript の配列には型がありませんが、Array のサブクラスを定義すれば、特定のデータ型の要素だけを格納する配列を作成できるようになります。 以下のセクションの例では、Array のサブクラスとして、第 1 パラメーターに指定されているデータ型の要素だけを格納する TypedArray というクラスを定義します。 TypedArray クラスは、Array クラスの拡張方法の例として示したにすぎず、いくつかの理由で運用目的には適さない場合があります。 第 1 に、コンパイル時ではなく、実行時に型チェックが行われます。 第 2 に、メソッドは簡単な変更によって例外をスローする可能性がありますが、TypedArray メソッドで不一致が見つかった場合に、その不一致は無視されて例外がスローされます。 第 3 に、このクラスでは、配列に任意の型の要素を挿入する配列アクセス演算子の使用を防止できません。 第 4 に、コーディングスタイルでは、パフォーマンスの最適化よりも単純さの方が優先されます。

注意: ここで説明したテクニックを使用すると、型指定された配列を作成できます。ただし、より良い方法は、Vector オブジェクトを使用することです。Vector インスタンスは実際に型指定された配列であり、Array クラスまたはどのサブクラスをも上回るパフォーマンスをもたらし、その他の点でも強化されます。この説明の目的は、Array サブクラスの作成方法を示すことです。

サブクラスの宣言

定義するクラスが Array のサブクラスであることを示すには、 extends キーワードを使用します。Array のサブクラスでは、Array と同様に dynamic 属性を使用します。これに従わないとサブクラスが正常に機能しません。

次のコードに示す TypedArray クラスの定義には、データ型を保持する定数、コンストラクターメソッド、配列要素の追加に使用できる 4 つのメソッドがあります。 ここでは各メソッドのコードを省略していますが、それらの詳細については以降のセクションで説明していきます。

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 {} 
}

4 つのオーバーライドしたメソッドはすべてが AS3 名前空間を使用し、 public 属性は使用していません。この例では、コンパイラーオプション -as3 の設定は true 、コンパイラーオプション -es の設定は false と仮定しているためです。これらの設定は、Adobe Flash Builder および Adobe Flash Professional のデフォルト設定です。

上級開発者でプロトタイプ継承の使用を優先したい場合は、TypedArray クラスに 2 つのマイナー変更を加え、コンパイラーオプション -es true に設定してコンパイルします。最初に、すべての出現箇所の override 属性を削除し、AS3 名前空間を public 属性で置換します。次に、出現するすべての 4 つの super の代わりに Array.prototype を使用します。

TypedArray コンストラクター

このコンストラクターを定義するにあたっては興味深い問題が 1 つあります。それは、任意の長さを持つリストをパラメーターとして受け付け、 さらに、配列を作成するためにパラメーターをスーパーコンストラクターに引き渡す必要があるということです。 パラメーターのリストを配列として渡せば、スーパーコンストラクターには Array 型のパラメーター 1 個として扱われてしまい、結果として配列の要素数は常に 1 になります。パラメーターリストをそのまま引き渡す場合の伝統的な処理方法として Function.apply() メソッドがあります。パラメーター配列をこのメソッドの第 2 パラメーターに指定すると、その要素を個々のパラメーターに展開して関数を実行させることができます。しかし、 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 コンストラクターから流用したコードで構成され、変更点は 4 つだけです。 1 つ目は、パラメーターリストに Class 型の新しい必須パラメーターを含め、配列のデータ型を指定できるようにしたことです。2 つ目は、コンストラクターに渡されるデータ型を dataType 変数に割り当てたことです。3 つ目は、 else ステートメントにおいて、 length プロパティの値は for ループの実行後に設定し、適切な型のパラメーターだけが length に含まれるようにしたことです。4 つ目は、オーバーライドした push() メソッドを for ループの本体の中で使用し、適切なデータ型のパラメーターだけを配列に追加するようにしたことです。次の例は、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 クラスのメソッドのうち、配列要素の追加に使用できる 4 つのメソッドをオーバーライドしています。 いずれのメソッドでも、適切なデータ型以外の要素が追加されるのを防ぐために型チェックを実行してから、 スーパークラスにある同じメソッドを呼び出します。

push() メソッドでは for..in ループで引数のリストを反復処理し、各引数の型チェックを行います。不適切な型のパラメーターがあれば、 splice() メソッドで args 配列から削除します。この for..in ループを終了した後は、 args には dataType 型の要素しか含まれていないことになります。それから、次のようにスーパークラスの push() に改変後の args 配列を渡して呼び出します。

    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() メソッドには任意の数の引数を指定できますが、最初の 2 つは必ず、インデックス番号と削除する要素の数を表す引数になります。このため、オーバーライドした splice() メソッドでは、 args のインデックス番号が 2 以降の配列要素に対してのみ型チェックを実行します。このコードでは、 for ループ内で splice() を再帰的に呼び出しているように見えますが、実際には再帰ではありません。 args の型は TypedArray ではなく Array なので、 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)); 
    } 
}