Klasa Array jest jedną z niewielu klas głównych, które nie są skończone, co oznacza, że możliwe jest utworzenie dla nich własnych podklas. W tej sekcji zamieszczono przykład tworzenia podklasy klasy Array oraz omówiono niektóre z problemów, jakie mogą pojawić się w tym procesie.
Tak jak wspomniano powyżej, tablice w języku ActionScript nie są typowane; można jednak utworzyć podklasę klasy Array, która będzie przyjmowała elementy tylko o zadanym typie danych. Przykład opisany w poniższych sekcjach ilustruje definiowanie podklasy Array o nazwie TypedArray ograniczającej jej elementy do wartości o typie danych określonym pierwszym parametrem. Klasa TypedArray jest rzadko przedstawiana jako przykład sposoby rozszerzenia klasy Array i może nie być odpowiednia do celów produkcyjnych z kilku względów. Po pierwsze, sprawdzanie typu odbywa się w czasie wykonania, nie zaś podczas kompilacji. Po drugie, gdy metoda TypedArray napotyka niezgodność, niezgodność ta jest ignorowana, a wyjątki nie są odrzucane, mimo że w przypadku metod taka modyfikacja w celu odrzucania wyjątków jest bardzo prosta. Po trzecie, klasa nie może uniemożliwić użycia operatora dostępu do tablicy do wstawienia wartości dowolnego typu do tabeli. Po czwarte, prostota konstrukcji tego kodu została osiągnięta kosztem optymalizacji jego działania.
Uwaga:
Za pomocą techniki opisanej w tej sekcji można utworzyć tablicę określonego typu. Jednak lepszym sposobem jest użycie obiektu Vector. Instancja Vector jest tablicą określonego typu i zapewnia wyższą wydajność oraz inne korzyści w porównaniu z klasą Array i jej podklasami. Celem niniejszej dyskusji jest zaprezentowanie sposobu tworzenia podklasy Array.
Deklarowanie podklas
Do wskazania, że dana klasa jest podklasą klasy Array, należy użyć słowa kluczowego
extends
. Podklasa klasy Array powinna korzystać z atrybutu
dynamic
, podobnie jak klasa Array. W przeciwnym razie podklasa użytkownika nie będzie działać prawidłowo.
W poniższym kodzie zaprezentowano definicję klasy TypedArray, która zawiera stałą przechowującą typ danych, metodę konstruktora oraz cztery metody umożliwiające dodawanie elementów do tablicy. Kod dla każdej z metod w tym przykładzie pominięto — został on dokładnie zacytowany i wyjaśniony w dalszych sekcjach.
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 {}
}
Wszystkie cztery nadpisane metody korzystają z przestrzeni nazw AS3 zamiast atrybutu
public
, ponieważ w tym przykładzie zakłada się, że opcję kompilatora
-as3
ustawiono na wartość
true
, a opcję kompilatora
-es
— na
false
. Są to ustawienia domyślne programów Adobe Flash Builder oraz Adobe Flash Professional.
Dla zaawansowanych programistów preferujących korzystanie z prototypów pozostawiono możliwość dokonania dwóch niewielkich zmian w klasie TypedArray w celu wymuszenia jej kompilacji z opcją kompilatora
-es
ustawioną na wartość
true
. Należy wówczas najpierw usunąć wszelkie wystąpienia atrybutu
override
oraz zastąpić przestrzeń nazw AS3 atrybutem
public
. Następnie należy zastąpić właściwość
Array.prototype
dla wszystkich czterech wystąpień wartości
super
.
Konstruktor TypedArray
Podklasa konstruktora wiąże się z pewnym interesującym, acz problematycznym, zagadnieniem; konstruktor bowiem musi przyjmować listę argumentów o arbitralnie ustalonej długości. Pytanie brzmi: w jaki sposób argumenty mają być przekazywane do superkonstruktora w celu utworzenia tablicy. W przypadku przekazania listy argumentów w postaci tablicy superkonstruktor uważa ją za pojedynczy argument typu Array, a wynikowa tablica ma zawsze długość 1 elementu. Tradycyjnym rozwiązaniem tej kwestii jest zastosowanie metody
Function.apply()
, która pobiera tablicę argumentów jako drugi parametr, lecz konwertuje ją na listę argumentów podczas wykonywania funkcji. Metody
Function.apply()
nie można jednak zastosować w przypadku konstruktorów.
Jedyną opcją jest ponowne utworzenie logiki konstruktora Array w konstruktorze TypedArray. W poniższym kodzie zaprezentowano algorytm używany w konstruktorze klasy Array, którego można użyć ponownie w konstruktorze podklasy 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]
}
}
}
}
Konstruktor TypedArray współużytkuje większość kody z konstruktora Array, przy tylko czterech zmianach w kodzie. Po pierwsze lista parametrów obejmuje nowy, wymagany parametr typu class, umożliwiający określenie typu danych tablicy. Następnie typ danych przekazany do konstruktora jest przypisywany do zmiennej
dataType
. Po trzecie, w instrukcji
else
wartość właściwości
length
jest przypisywana po pętli
for
w taki sposób, że wartość
length
obejmuje wyłącznie argumenty odpowiedniego typu. Po czwarte, w treści pętli
for
używana jest zastąpiona wersja metody
push()
, przez co tylko argumenty o właściwym typie danych są dodawane do tablicy. Poniższy przykład demonstruje działanie konstruktora 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, metody zastąpione przez
Klasa TypedArray zastępuje cztery metody klasy Array odpowiadające za dodawanie elementów do tablicy. W każdym z przypadków metoda zastąpiona dodaje kontrolę typu uniemożliwiającą dodawanie do tablicy elementów o niewłaściwym typie danych. Podobnie każda metoda wywołuje wersję podklasy samej siebie.
Metoda
push()
iteruje argumenty listy za pomocą pętli
for..in
, sprawdzając jednocześnie typ każdego z argumentów. Każdy z argumentów, który nie należy do właściwego typu, jest usuwany z tablicy
args
za pomocą metody
splice()
. Po zakończeniu pętli
for..in
tablica
args
zawiera wartości tylko jednego typu danych:
dataType
. Następnie wywoływana jest należąca do klasy nadrzędnej wersja metody
push()
dla zaktualizowanej tablicy
args
, zgodnie z poniższym kodem przykładowym.
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));
}
Metoda
concat()
tworzy tymczasowo tablicę TypedArray o nazwie
passArgs
do przechowywania argumentów, które pomyślnie przeszły etap sprawdzania typów. Umożliwia to ponowne użycie kodu sprawdzania typów istniejącego w metodzie
push()
. Pętla
for..in
iteruje tablicę
args
i wywołuje metodę
push()
dla każdego z argumentów. Ze względu na to, że zmienna
passArgs
ma typ TypedArray, wykonywana jest wersja TypedArray metody
push()
. Następnie metoda
concat()
wywołuje własną wersję klasy nadrzędnej, zgodnie z poniższym kodem.
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));
}
Metoda
splice()
pobiera dowolną listę argumentów, lecz dwa pierwsze z nich zawsze odnoszą się do numeru indeksu oraz liczby elementów do usunięcia. Wyjaśnia to, dlaczego zastąpiona metoda
splice()
sprawdza typy wyłącznie dla elementów tablicy
args
o indeksie 2 i kolejnych indeksach. Jedną z interesujących kwestii w kodzie jest wrażenie powstania wywołania rekurencyjnego metody
splice()
wewnątrz pętli
for
. W rzeczywistości nie jest to wywołanie rekurencyjne, ponieważ tablica
args
jest typu Array, nie zaś TypedArray, co oznacza, że wywołanie metody
args.splice()
jest tak naprawdę wywołaniem wersji klasy nadrzędnej tej metody. Po zakończeniu pętli
for..in
tablica
args
zawiera wyłącznie wartość prawidłowego typu w położeniach indeksu 2 i wyższych, a metoda
splice()
wywołuje własną wersję z klasy nadrzędnej, tak jak w poniższym kodzie.
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));
}
Metoda
unshift()
, dodająca elementy na początek tablicy, przyjmuje również dowolną listę argumentów. Zastępowana metoda
unshift()
korzysta z algorytmu bardzo podobnego do tego używanego przez metodę
push()
, tak jak w poniższym przykładowym kodzie.
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));
}
}