Класс 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));
}
}