Utökning av klassen Array

Flash Player 9 och senare, Adobe AIR 1.0 och senare

Klassen Array är en av de få huvudklasserna som inte är slutgiltig, vilket innebär att du kan skapa egna underklasser till klassen Array. I detta avsnitt finns exempel på hur du skapar en underklass till klassen Array. Dessutom beskrivs något av vad som kan hända under processens gång.

Som nämnts tidigare är arrayer i ActionScript inte ”typade”, men du kan skapa en underklass av klassen Array som endast accepterar element med en viss datatyp. I exemplen i följande avsnitt definieras en Array-underklass med namnet TypedArray, där endast värden som innehåller datatypen som angavs i den första parametern tas emot. Klassen TypedArray visas bara som ett exempel på hur du kan utöka klassen Array och är av många anledningar inte lämplig i verkliga tillämpningar. För det första görs typkontrollen under körningen och inte vid kompileringen. För det andra ignoreras felmatchningen om den upptäcks i en TypedArray-metod och inget undantag genereras trots att metoden enkelt kan förändras så att detta görs. För det tredje kan klassen inte förhindra att arrayåtkomstoperatorn infogar värden med någon annan datatyp i arrayen. Slutligen favoriserar kodstilen enkelhet framför optimal prestanda.

Obs! Du kan använda den teknik som beskrivs här när du skapar en typbestämd array. Det är emellertid bättre att använda ett Vector-objekt. En Vector-instans är en äkta typbestämd array, som har bättre prestanda och fler fördelar än klassen Array och underklasser. Syftet här är att visa hur du skapar en Array-underklass.

Deklarera underklasser

Använd nyckelordet extends för att visa att en klass är en underklass till Array. I en Array-underklass ska attributet dynamic användas precis som i klassen Array. Annars fungerar underklassen inte som den ska.

I följande exempel visas definitionen av klassen TypedArray, som innehåller en konstant för datatypen, en konstruktormetod och fyra metoder för att lägga till element till arrayen. Koden för varje metod har utelämnats i detta exempel, men den beskrivs och förklaras i sin helhet i avsnitten som följer:

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

I alla de fyra åsidosatta metoderna används AS3-namnutrymmet i stället för attributet public, eftersom vi i detta exempel antar att kompilatoralternativet -as3 är true och att kompilatoralternativet -es är false. Detta är standardinställningarna för Adobe Flash Builder och AdobeFlashProfessional.

Om du är en van utvecklare, som föredrar att använda prototyparv, kan du göra två smärre förändringar i klassen TypedArray så att den kan kompileras med kompilatoralternativet -es som true. Börja med att ta bort alla förekomster av attributet override och ersätt AS3-namnutrymmet med attributet public. Därefter ersätter du alla fyra förekomster av super med Array.prototype.

Konstruktorn TypedArray

Underklasskonstruktorn utgör en intressant utmaning eftersom den måste kunna godta en argumentlista med godtycklig längd. Utmaningen är hur argumenten ska överföras till superkonstruktorn så att arrayen skapas. När du skickar en argumentlista som en array, betraktas den av superkonstruktorn som ett enskilt argument av typen Array och den resulterande arrayen kommer då alltid att vara ett element långt. Det traditionella sättet att hantera överförda argumentlistor är att använda metoden Function.apply(), som tar en array med argument som dess andra parameter men konverterar den till en argumentlista när funktionen körs. Tyvärr går det inte att använda metoden Function.apply() tillsammans med en konstruktor.

Det enda som återstår att göra är att återskapa logiken i Array-konstruktorn i konstruktorn TypedArray. I nästa exempel visas algoritmen som används i klassen Arrays konstruktor, som sedan återanvänds i din Array-underklasskonstruktor:

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

Koden i konstruktorn TypedArray har mycket gemensamt med koden för Array-konstruktorn, men fyra ändringar måste göras. Till att börja med innehåller parameterlistan en ny obligatorisk parameter med typen Class, som gör att det går att ange arrayens datatyp. För det andra är datatypen som skickas till konstruktorn tilldelad till variabeln dataType. För det tredje är värdet för egenskapen length i else-satsen tilldelat efter for-slingan så att length endast kommer att innehålla argument med rätt typ. Slutligen används i texten i for-slingan den version av metoden push() som åsidosätter argument så att endast de med rätt datatyp läggs till i arrayen. I följande exempel visas konstruktorfunktionen 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 – metoder för åsidosättande

Klassen TypedArray åsidosätter fyra av klassen Arrays metoder för att lägga till element till en array. I vart och ett av fallen används i den åsidosättande metoden en typkontroll som förhindrar att element med fel datatyp läggs till. Detta innebär att varje metod anropar den egna överordnade klassens version.

Metoden push() itererar igenom argumentlistan med en for..in-slinga och en typkontroll görs av varje argument. De argument som inte har rätt typ tas bort från arrayen args med metoden splice(). När for..in-slingan är slut innehåller arrayen args endast värden med typen dataType. Den överordnade klassens version av push() anropas sedan med den uppdaterade versionen av arrayen args, vilket visas i följande exempel:

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

Med metoden concat() skapas en temporär TypedArray med namnet passArgs. Den används för att lagra argument som har accepterats i typkontrollen. Detta medger att det går att återanvända koden för typkontrollen i metoden push(). Med en for..in-slinga itererar du genom args-arrayen och anropar push() vid varje argument. Eftersom passArgs har typen TypedArray körs TypedArray-versionen av push(). Metoden concat() anropar sedan den egna överordnade klassens version, vilket framgår i följande exempel:

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

Metoden splice() tar en godtycklig argumentlista, men de två första argumenten refererar alltid till ett indexvärde och antalet element som ska tas bort. Detta är orsaken till att det görs en typkontroll av elementen i arrayen args endast vid indexposition 2 och högre i den åsidosatta metoden splice(). En intressant aspekt av koden är att det verkar finnas ett rekursivt anrop till splice() i for-slingan, men detta är trots allt inget rekursivt anrop eftersom args har type Array och inte TypedArray. Detta innebär att anropet args.splice() är ett anrop till den överordnade klassens version av metoden. När for..in-slingan är klar innehåller arrayen args endast värden med rätt typ i indexpositionerna 2 eller högre och splice()-anropet är till den egna överordnade klassens version, vilket visas i följande exempel:

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

För metoden unshift(), där element läggs till i början av en array, accepteras också en godtycklig argumentlista. I den åsidosatta metoden unshift() används en algoritm som mycket påminner om den som används i metoden push(), vilket visas i följande exempel:

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