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