Расширение класса Array

Flash Player 9 и более поздних версий, Adobe AIR 1.0 и более поздних версий

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