高度なトピック

Array クラスの拡張

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

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

サブクラスの宣言

定義するクラスが 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 Flex Builder 2 および Adobe Flash CS3 Professional のデフォルト設定です。詳細については、AS3 名前空間を参照してください。

ヒント

 

上級開発者でプロトタイプ継承の使用を優先したい場合は、TypedArray クラスに 2 つのマイナー変更を加え、コンパイラオプション -estrue に設定してコンパイルします。最初に、すべての出現箇所の 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++)
            {
                // 型チェックは 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)
        {
            // 型チェックは 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));
    }
}

 

このページに新しいコメントが追加された場合に、電子メールでの通知を希望する。 | コメントレポート

現在のページ: http://livedocs.adobe.com/flash/9.0_jp/main/00000093.html