Класс Array — это один из нескольких ключевых нефинальных классов, то есть он позволяет создавать собственные подклассы класса Array. В этом разделе приводится пример того, как создавать подкласс класса Array и рассказывается о некоторых проблемах, которые могут возникнуть при выполнении этой операции.
Как было сказано выше, массивы в языке ActionScript не типизированные, но можно создать подкласс класса Array, который будет принимать элементы только определенного типа данных. В примере в последующих разделах определяется подкласс класса Array с именем TypedArray, в первом параметре которого накладывается ограничение на тип данных элементов. Класс TypedArray представлен исключительно в качестве примера того, как расширить класс Array, и может не подойти для практического применения по ряду причин. Во-первых, проверка типа производится во время выполнения, а не при компиляции. Во-вторых, когда метод TypedArray сталкивается с несовпадением, он игнорирует его, а исключение при этом не генерируется, хотя можно легко модифицировать методы, чтобы они генерировали исключения. В-третьих, класс не может предотвратить использование оператора доступа к массиву для вставки значений какого-либо типа в массив. В-четвертых, в силу стиля кода предпочтение отдается простоте, а не оптимизации.
Примечание.
Описанный здесь прием можно использовать для создания типизированного массива. Однако для этого лучше использовать объект Vector. Экземпляр Vector является настоящим типизированным массивом и превосходит класс Array и его подклассы по производительности и другим показателям. В целях данного обсуждения демонстрируется создание подкласса Array.
Объявление подкласса
Используйте ключевое слово
extends
для обозначения того, что класс является подклассом класса Array. Подкласс класса должен использовать атрибут
dynamic
, как и класс Array. В противном случае подкласс будет работать некорректно.
В приведенном ниже коде дается определение класса TypedArray, содержащего константу для хранения типа данных, метод конструктора и четыре метода для добавления элементов в массив. В этом примере не приводится код для каждого из методов, но он подробным образом освещается в последующих разделах:
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 {}
}
Четыре переопределенных метода используют пространство имен AS3 вместо атрибута
public
, потому что в данном примере мы исходим из того, что параметр компилятора
-as3
=
true
, а параметр компилятора
-es
=
false
. Это настройки по умолчанию для Adobe Flash Builder и AdobeFlashProfessional.
Если вы опытный разработчик и предпочитаете использовать прототипное наследование, то можете внести две небольших правки в класс TypedArray, чтобы компиляция выполнялась с помощью параметра компиляции
-es
со значением
true
. Во-первых, удалите все вхождения атрибута
override
и замените пространство имен AS3 атрибутом
public
. Во-вторых, замените все четыре вхождения
super
на
Array.prototypesuper
.
Конструктор TypedArray
Конструктор подкласса ставит перед разработчиком интересную задачу, так как конструктор должен принимать список аргументов любой длины. Сложность состоит в том, как передавать аргументы суперконструктору для создания массива. Если список аргументов передается как массив, суперконструктор расценивает его как единственный аргумент типа Array, и итоговый массив всегда состоит из 1 элемента. Традиционно список аргументов передается с помощью метода
Function.apply()
, который принимает массив аргументов в качестве второго параметра, но преобразует его в список аргументов при выполнении функции. К сожалению, метод
Function.apply()
нельзя использовать с конструкторами.
Единственный оставшийся вариант — воссоздать логику конструктора Array в конструкторе TypedArray. В приведенном ниже коде показан алгоритм, используемый в конструкторе класса Array, который можно использовать и в конструкторе подкласса 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]
}
}
}
}
Большая часть кода конструктора TypedArray идентична коду конструктора класса Array, за исключением четырех отличий. Во-первых, список параметров включает новый обязательный параметр типа Class, позволяющий задавать тип данных массива. Во-вторых, тип данных, передаваемых конструктору, назначается переменной
dataType
. В-третьих, инструкция
else
— значение свойства
length
— назначается после цикла
for
, поэтому
length
включает только аргументы правильного типа. В-четвертых, внутри цикла
for
используется переопределенный вариант метода
push()
, поэтому к массиву добавляются только аргументы правильного типа данных. В примере ниже показана функция конструктора 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
Класс TypedArray переопределяет четыре метода класса Array, которые позволяют добавлять элементы в массив. В каждом случае переопределенный метод добавляет проверку типа, чтобы гарантировать только добавление элементов правильного типа. Следовательно, каждый метод вызывает вариант самого себя для суперкласса.
Метод
push()
выполняет итерацию в списке аргументов с помощью цикла
for..in
и проверку типа для каждого аргумента. Любой аргумент, не соответствующий правильному типу, удаляется из массива
args
методом
splice()
. После окончания цикла
for..in
в массиве
args
остаются только значения типа
dataType
. Затем вызывается вариант метода
push()
для суперкласса с обновленным массивом
args
, как показано в коде ниже:
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));
}
Метод
concat()
создает временный TypedArray с именем
passArgs
для хранения аргументов, передающих проверку типа. Это позволяет повторно использовать процедуру проверки типа метода
push()
. Цикл
for..in
выполняет итерацию в массиве
args
и вызывает метод
push()
для каждого аргумента. Так как типом
passArgs
является TypedArray, выполняется метод
push()
для TypedArray. Метод
concat()
далее вызывает собственный вариант для суперкласса, как показано ниже:
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));
}
Метод
splice()
принимает произвольный список аргументов, но первые два аргумента всегда обозначают индекс и число удаляемых элементов. Именно поэтому переопределенный метод
splice()
выполняет проверку типа только для элементов массива
args
с индексами 2 и более. Отдельного внимания заслуживает то, что в коде рекурсивно вызывается метод
splice()
внутри цикла
for
, но это не рекурсивный вызов, потому что
args
относится к типу Array, а не TypedArray, следовательно, вызов
args.splice()
является вызовом варианта метода для суперкласса. После окончания цикла
for..in
в массиве
args
остаются значения правильного типа с индексами 2 и выше, а метод
splice()
вызывает свою версию для суперкласса, как показано ниже:
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));
}
Метод
unshift()
, добавляющий элементы в начало массива, также принимает произвольный список аргументов. Переопределенный метод
unshift()
использует алгоритм, очень похожий на метод
push()
, как показано в приведенном ниже коде:
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));
}
}