Extending the Array class

Flash Player 9 and later, Adobe AIR 1.0 and later

The Array class is one of the few core classes that is not final, which means that you can create your own subclass of Array. This section provides an example of how to create a subclass of Array and discusses some of the issues that can arise during the process.

As mentioned previously, arrays in ActionScript are not typed, but you can create a subclass of Array that accepts elements of only a specific data type. The example in the following sections defines an Array subclass named TypedArray that limits its elements to values of the data type specified in the first parameter. The TypedArray class is presented merely as an example of how to extend the Array class and may not be suitable for production purposes for several reasons. First, type checking occurs at run time rather than at compile time. Second, when a TypedArray method encounters a mismatch, the mismatch is ignored and no exception is thrown, although the methods can be easily modified to throw exceptions. Third, the class cannot prevent the use of the array access operator to insert values of any type into the array. Fourth, the coding style favors simplicity over performance optimization.

Note: You can use the technique described here to create a typed array. However, a better approach is to use a Vector object. A Vector instance is a true typed array, and provides performance and other improvements over the Array class or any subclass. The purpose of this discussion is to demonstrate how to create an Array subclass.

Declaring the subclass

Use the extends keyword to indicate that a class is a subclass of Array. A subclass of Array should use the dynamic attribute, just as the Array class does. Otherwise, your subclass will not function properly.

The following code shows the definition of the TypedArray class, which contains a constant to hold the data type, a constructor method, and the four methods that are capable of adding elements to the array. The code for each method is omitted in this example, but is delineated and explained fully in the sections that follow:

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

The four overridden methods all use the AS3 namespace instead of the public attribute because this example assumes that the compiler option -as3 is set to true and the compiler option -es is set to false. These are the default settings for Adobe Flash Builder and for AdobeFlashProfessional.

If you are an advanced developer who prefers to use prototype inheritance, you can make two minor changes to the TypedArray class to make it compile with the compiler option -es set to true. First, remove all occurrences of the override attribute and replace the AS3 namespace with the public attribute. Second, substitute Array.prototype for all four occurrences of super.

TypedArray constructor

The subclass constructor poses an interesting challenge because the constructor must accept a list of arguments of arbitrary length. The challenge is how to pass the arguments on to the superconstructor to create the array. If you pass the list of arguments as an array, the superconstructor considers it a single argument of type Array and the resulting array is always 1 element long. The traditional way to handle pass-through argument lists is to use the Function.apply() method, which takes an array of arguments as its second parameter but converts it to a list of arguments when executing the function. Unfortunately, the Function.apply() method cannot be used with constructors.

The only option left is to recreate the logic of the Array constructor in the TypedArray constructor. The following code shows the algorithm used in the Array class constructor, which you can reuse in your Array subclass constructor:

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

The TypedArray constructor shares most of the code from the Array constructor, with only four changes to the code. First, the parameter list includes a new required parameter of type Class that allows specification of the array’s data type. Second, the data type passed to the constructor is assigned to the dataType variable. Third, in the else statement, the value of the length property is assigned after the for loop so that length includes only arguments that are the proper type. Fourth, the body of the for loop uses the overridden version of the push() method so that only arguments of the correct data type are added to the array. The following example shows the TypedArray constructor function:

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 overridden methods

The TypedArray class overrides the four methods of the Array class that are capable of adding elements to an array. In each case, the overridden method adds a type check that prevents the addition of elements that are not the correct data type. Subsequently, each method calls the superclass version of itself.

The push() method iterates through the list of arguments with a for..in loop and does a type check on each argument. Any argument that is not the correct type is removed from the args array with the splice() method. After the for..in loop ends, the args array contains values only of type dataType. The superclass version of push() is then called with the updated args array, as the following code shows:

    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)); 
    }

The concat() method creates a temporary TypedArray named passArgs to store the arguments that pass the type check. This allows the reuse of the type check code that exists in the push() method. A for..in loop iterates through the args array, and calls push() on each argument. Because passArgs is typed as TypedArray, the TypedArray version of push() is executed. The concat() method then calls its own superclass version, as the following code shows:

    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)); 
    }

The splice() method takes an arbitrary list of arguments, but the first two arguments always refer to an index number and the number of elements to delete. This is why the overridden splice() method does type checking only for args array elements in index positions 2 or higher. One point of interest in the code is that there appears to be a recursive call to splice() inside the for loop, but this is not a recursive call because args is of type Array rather than TypedArray, which means that the call to args.splice() is a call to the superclass version of the method. After the for..in loop concludes, the args array contains only values of the correct type in index positions 2 or higher, and splice() calls its own superclass version, as shown in the following code:

    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)); 
    }

The unshift() method, which adds elements to the beginning of an array, also accepts an arbitrary list of arguments. The overridden unshift() method uses an algorithm very similar to that used by the push() method, as shown in the following example code:

    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)); 
    } 
}