Estensione della classe Array

Flash Player 9 e versioni successive, Adobe AIR 1.0 e versioni successive

La classe Array è una delle poche classi principali che non è finale; pertanto è possibile creare delle sottoclassi della classe Array. In questa sezione viene fornito un esempio che illustra come creare una sottoclasse di Array e vengono discussi alcuni dei problemi che potrebbero verificarsi nel corso di tale procedura.

Come accennato precedentemente, gli array in ActionScript non sono tipizzati, tuttavia è possibile creare sottoclassi di Array che accettano solo elementi di uno specifico tipo di dati. Nell'esempio delle sezioni che seguono viene definita una sottoclasse di Array denominata TypedArray che limita i suoi elementi a valori del tipo di dati specificato nel primo parametro. La classe TypedArray viene presentata solo come esempio di come è possibile estendere la classe Array e non è idonea a scopi produttivi per varie ragioni. In primo luogo, la verifica dei tipi viene eseguita in fase di runtime anziché in fase di compilazione. Secondo, quando un metodo TypedArray rileva un'errata corrispondenza, questa viene ignorata e non viene generata alcuna eccezione, anche se i metodi possono essere facilmente modificati per generare eccezioni. Terzo, la classe non è in grado di impedire l'uso dell'operatore di accesso all'array per l'inserimento di valori di qualunque tipo nell'array. Quarto, lo stile di codifica favorisce la semplicità rispetto all'ottimizzazione delle prestazioni.

Nota: potete utilizzare la tecnica qui descritta per creare un array tipizzato. Tuttavia, un approccio migliore consiste nell'utilizzare un oggetto Vector. Un'istanza Vector, infatti, è un vero array tipizzato e consente delle prestazioni e degli altri vantaggi rispetto alla classe Array e a qualsiasi altra sottoclasse. Lo scopo di questa discussione è quello di dimostrare come creare una sottoclasse Array.

Dichiarazione di una sottoclasse

Utilizzate la parola chiave extends per indicare che una classe è una sottoclasse di Array. Le sottoclassi di Array devono usare l'attributo dynamic , esattamente come la classe Array, altrimenti non funzionano correttamente.

Il codice seguente illustra la definizione della classe TypedArray, contenente una costante per la memorizzazione del tipo di dati, un metodo della funzione di costruzione e i quattro metodi che consentono di aggiungere elementi all'array. Nell'esempio viene omesso il codice di ciascun metodo, che però verrà illustrato dettagliatamente nelle sezioni che seguono:

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

Tutti e quattro i metodi sostituiti usano lo spazio dei nomi di AS3 anziché l'attributo public , perché in questo esempio si presuppone che l'opzione del compilatore -as3 sia impostata su true e l'opzione del compilatore -es sia impostata su false . Queste sono le impostazioni predefinite per Adobe Flash Builder e AdobeFlashProfessional.

Se siete uno sviluppatore esperto e preferite utilizzare l'ereditarietà di prototipi, potete apportare due piccole modifiche alla classe TypedArray affinché venga compilata con l'opzione del compilatore -es impostata su true . In primo luogo, rimuovete tutte le occorrenze dell'attributo override e sostituite lo spazio dei nomi di AS3 con l'attributo public . Quindi, sostituite Array.prototype per tutte le occorrenze di super .

Funzione di costruzione TypedArray

La funzione di costruzione della sottoclasse pone un interessante problema in quanto la funzione di costruzione deve accettare un elenco di argomenti di lunghezza arbitraria. Il problema consiste nell'assegnare gli argomenti al supercostruttore per creare l'array. Se passate l'elenco di argomenti come array, il supercostruttore lo considera come un unico argomento di tipo Array e l'array risultante contiene sempre un solo elemento. Per gestire in modo tradizionale elenchi di argomenti da trasmettere potete usare il metodo Function.apply() , che considera un array di argomenti come suo secondo parametro, ma lo converte poi in un elenco di argomenti durante l'esecuzione della funzione. Purtroppo, però, il metodo Function.apply() non può essere utilizzato con funzioni di costruzione.

La sola opzione possibile, quindi, è quella di ricreare la logica della funzione di costruzione di Array nella funzione di costruzione TypedArray. Il codice seguente illustra l'algoritmo utilizzato nella funzione di costruzione della classe Array, che è possibile riutilizzare nella funzione di costruzione della sottoclasse di 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]  
            } 
        } 
    } 
}

La funzione di costruzione TypedArray condivide la maggior parte del codice con la funzione di costruzione Array, con solo qualche leggera differenza. In primo luogo, l'elenco dei parametri include un nuovo parametro richiesto di tipo Class che consente di specificare il tipo di dati dell'array. Secondo, il tipo di dati passati alla funzione di costruzione viene assegnato alla variabile dataType . Terzo, nell'istruzione else , il valore della proprietà length viene assegnato dopo il ciclo for , affinché la proprietà length includa unicamente gli argomenti del tipo corretto. Quarto, il corpo del ciclo for usa la versione sostituita del metodo push() ; in tal modo solo gli argomenti del tipo di dati corretto vengono aggiunti all'array. Nell'esempio seguente è illustrata la funzione di costruzione 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; 
        } 
    } 
}

Metodi sostituiti di TypedArray

La classe TypedArray sostituisce i quattro metodi della classe Array che consentono di aggiungere elementi a un array. Ognuno dei metodi sostituiti permette di aggiungere un controllo di tipo che impedisce l'inserimento di elementi appartenenti a un tipo di dati non corretto. Quindi, ogni metodo chiama la versione di se stesso della superclasse.

Il metodo push() esegue iterazioni all'interno dell'elenco degli argomenti con una funzione ciclica for..in ed effettua un controllo di tipo per ogni argomento. Se vengono rilevati argomenti di tipo non corretto, essi vengono rimossi dall'array args mediante il metodo splice() . Al termine della funzione ciclica for..in , l'array args conterrà valori solo di tipo dataType . La versione della superclasse del metodo push() viene quindi chiamata con l'array args aggiornato, come illustrato dal codice seguente:

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

Il metodo concat() consente di creare un array TypedArray temporaneo chiamato passArgs in cui vengono memorizzati gli argomenti che superano il controllo di tipo. Ciò consente di riutilizzare il codice per il controllo di tipo già esistente nel metodo push() . Il ciclo for..in esegue un'iterazione attraverso l'array args e chiama il metodo push() su ciascun argomento. Poiché passArgs è tipizzato come TypedArray, viene eseguita la versione TypedArray di push() . Il metodo concat() chiama quindi la versione di se stesso della superclasse, come illustrato nel codice seguente:

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

Sebbene il metodo splice() utilizzi un elenco arbitrario di argomenti, i primi due argomenti fanno sempre riferimento a un numero di indice e al numero di elementi da eliminare. Ecco perché il metodo splice() sostituito esegue il controllo di tipo unicamente per gli elementi dell'array args nella posizione di indice 2 o superiore. Un aspetto interessante del codice è che all'interno della funzione ciclica for sembra verificarsi una chiamata ricorsiva al metodo splice() ; in realtà, non si tratta di una chiamata ricorsiva in quanto l'array args è di tipo Array e non di tipo TypedArray, quindi la chiamata a args.splice() è una chiamata alla versione del metodo della superclasse. Al termine della funzione ciclica for..in , l'array args contiene unicamente valori di tipo corretto in posizione di indice 2 o superiore e il metodo splice() chiama la versione di se stesso della superclasse, come illustrato nel codice seguente:

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

Anche il metodo unshift() , che consente di aggiungere elementi all'inizio di un array, accetta un elenco arbitrario di argomenti. Il metodo sostituito unshift() usa un algoritmo molto simile a quello usato dal metodo push() , come illustrato nell'esempio di codice seguente:

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