19 мая 2015 г.

Массивы. Часть 4 – практика работы с массивами.

Теперь еще немного попрактикуемся с массивами, рассмотрим некоторые методы из стандартных библиотек JDK, а так же обсудим некоторые моменты, которые остались за рамками предыдущих постов.

В стандартной библиотеке JDK существует класс java.util.Arrays содержащий методы для работы с массивами. Мы тут рассмотрим конечно далеко не все, а лишь некоторые, но ссылка у вас уже есть, так что все остальное можете изучить сами.

Сперва еще раз затронем тему ссылок. Разберем простой пример сравнения двух массивов:

int[] a = { 1, 2 };
int[] b = { 1, 2 };
println("a==b is " + (a == b)); // сравнивает ссылки
println("a.equals(b) is " + a.equals(b)); // сравнивает ссылки

Хотя содержимое массивов a и b равно, но если мы просто сравним a==b, то результатом будет false, поскольку в данном случае произойдет сравнение ссылок, которые будут разными, так как указывают на разные объекты. Метод equals существует для многих классов Java и как правило он сравнивает содержимое этих объектов, но для массивов он почему-то тоже сравнивает ссылки.

Для сравнения одномерных массивов существует метод Arrays.equals, который сравнивает содержимое массивов. Для сравнения многомерных массивов есть метод Arrays.deepEquals.

Метод 

Arrays.equals(a, b)

в результате сравнения массивов а и b выдаст true.

Для копирования одномерного массива есть метод Arrays.copyOf, к сожалению, для многомерных массивов такого метода нет. По существу, если посмотреть код данного метода, он использует системный метод java.lang.System.arraycopy, который кстати сказать, работает очень быстро, но к сожалению тоже применим только для одномерных массивов.

С методом Arrays.copyOf мы уже сталкивались, когда рассматривали программу сортировки одномерного массива Array03.java. Методы Arrays.toString и Arrays.deepToString преобразуют одномерные и многомерные массивы  соответственно в строку, более менее удобную для вывода на консоль. С ними мы уже сталкивались в предыдущем посте.

Метода Arrays.sort сортирует одномерный массив, метода для сортировки многомерных массивов в стандартной библиотеке Java нет.

Метод Arrays.binarySearch ищет в одномерном массиве заданное значение и возвращает его индекс.

Вообще класс Arrays содержит достаточно много полезных методов, поэтому, прежде чем писать свой, можно заглянуть в него и посмотреть а нет ли уже нужного.

Теперь немножко практики чтобы закрепить все вышесказанное:

A00023

Данная программа может сгенерировать следующий вывод:

A00024

Тут все просто, поэтому идем сразу дальше к методу java.lang.System.arraycopy. Он очень интересен, так как работает очень быстро, быстрее чем копирование при помощи цикла for. Рассмотрим программу копирования массива состоящего из 10 миллионов значений int, каждый элемент массива содержит значение типа int равное индексу данного элемента. Даже на заполнение данного массива значениями требуется некоторое время. Далее происходит сортировка циклом for и методом java.lang.System.arraycopy. И выводится время затраченное в обоих методах. Как говорится почувствуйте разницу.

Данная программа может сгенерировать такой вывод (зависит от мощности вашего компьютера):

A00025

При запуске этой программы, может показаться, что она немного подзависла. Но терпение господа! Заполнить массив 10 миллионами значений и потом их еще два раза скопировать – это занимает время.

Еще стоит упомянуть о возможности передавать массивы в методы. Хоть пока мы методы и не рассматривали, так как не рассматривали классы, но все же поскольку это связано с массивами, то рассмотрим тут. Если что-то не понятно, то просто намотайте на ус и просто имейте в виду что есть такая возможность как передача в метод переменного количества аргументов (Varargs). Этот синтаксис стал доступен а Java 5. Так же стоит отметить, что данный синтаксис применим только к одномерным массивам.

Для примера рассмотрим метод возвращающий максимальное число из переданного в него одномерного массива чисел.

A00026

В строке 17 описан метод без использования синтаксиса Varargs. В строке 24 в описании метода используется синтаксис Varargs. Соответственно и вызываются эти методы по разному (строки 9 и 13 соответственно), хотя и выполняют абсолютно одну и ту же задачу, просто в случае использования синтаксиса Varargs код более читаем и понятен.

По существу в методе max_new numbers это тот же одномерный массив чисел и соответственно с ним можно работать как с массивом.

Поскольку классы и методы мы еще не изучали я немного поясню данный код.

Метод main() заканчивается на строке 15, то есть выполнение программы заканчивается на этой строке.

После строки 15 идет объявление методов max_old и max_new, которые вызываются в строках 9 и 13 соответственно.

Данная программа генерирует следующий вывод:

A00027

Отметим две важные особенности этой программы. Во-первых, как уже было сказано, внутри метода max_new()  переменная numbers действует как массив. Это обусловлено тем, что numbers является массивом. Синтаксическая конструкция ...  просто указывает компилятору, что метод будет использовать переменное количество аргументов, и что эти аргументы будут храниться в массиве, на который ссылается переменная numbers.

Метод max_new() может вызываться с различным количеством аргументов, в том числе, и вовсе без аргументов. Аргументы автоматически помещаются в массив и ссылка на него передается переменной numbers. В случае отсутствия аргументов длина массива равна нулю.

Наряду с параметром переменной длины массив может содержать “нормальные” параметры. Однако параметр переменной длины должен быть последним параметром, объявленным в методе. Например, следующее объявление метода вполне допустимо:

int doIt(int a, int b, double c, int... vals)

В данном случае первые три аргумента, указанные в обращении к методу doIt() , соответствуют первым трем параметрам. Все остальные аргументы считаются принадлежащими параметру vals.

Помните, что параметр vararg должен быть последним. Например, следующее объявление записано неправильно:

int doIt(int a, int b, double c, int... vals, boolean stopFlag) // Ошибка!

В этом примере предпринимается попытка объявления обычного параметра после параметра типа vararg, что недопустимо.

Существует еще одно ограничение, о котором следует знать: метод должен содержать только один параметр типа varargs. Например, следующее объявление также неверно:

int doIt(int a, int b, double c, int... vals, double... morevals) // Ошибка!

Попытка объявления второго параметра типа vararg недопустима.

A00029Рассмотрим еще одну простую программу с varargs представленную справа.

В строке 9 происходит вызов метода varArgs, без аргумента переменной длины, то есть он отсутствует.

В строке 10 в varargs передан один аргумент, ав строке 12 три.

Так же, как видите, как аргумент varargs можно передавать и одномерный массив (строка 15).

В varargs можно передать и многомерный массив, но из него будет взято только первое измерение – первый индекс.

Данная программа сгенерирует следующий вывод:

A00030

Я думаю сразу следует рассказать и о возможности перегрузки методов использующих varargs и подводные камни которые могут при этом быть, хотя сейчас, возможно, вам и не понятно, что такое перегрузка методов, но все же стоит завязать узелок на память.

Если сказать по простому, то перегрузка методов, это объявление двух или более методов с одинаковым названием, но разным количеством и/или разными типами данных принимаемых аргументов. Это позволяет использовать одно название метода, но передавать туда аргументы разных типов данных и/или разное количество аргументов. Лучше все это посмотреть на примере и тогда все станет более или менее ясно.

A00031

В строках с 9 по 14 происходит вызов перегруженного метода varArgs.

Строка 11 содержит ошибку, так как нет метода varArgs, который бы принимал как аргументы строку и varargs параметры типа double. Это строку я вставил для пущего понимания происходящего.

С 17 строки идет объявление перегруженного метода varArgs.

Обратите пристальное внимание на то, какие аргументы принимает данный перегруженный метод и как он вызывается. Особенно на строку 23. В ней не используется varargs!

Хотя сейчас это может быть и не особо понятно, но на подкорочке пусть будет.

Программа генерирует следующий вывод:

A00032

На заметку! Метод, поддерживающий varargs, может быть перегружен также методом, который не поддерживает эту функциональную возможность (строка 23). Например, в приведенной программе метод varArgs()  перегружен методом varArgs(int numbers). Эта специализированная версия вызывается только при наличии одного аргумента int. В случае передаче методу двух и более аргументов int программа будет использовать varargs-версию метода varArgs(int... numbers) определенную в строке 27.

GrabliПри перегрузке метода, принимающего аргумент переменной длины, могут случаться непредвиденные ошибки. Они связаны с неопределенностью, которая может возникать при вызове перегруженного метода с аргументом переменной длины.

 

Например, рассмотрим следующую программу:

A00033

Вызов метода varArgs в строке 16 вызовет ошибку компиляции. Поскольку параметр типа vararg может быть пустым, этот вызов может быть преобразован в обращение к varArgs(int...), varArgs(double…) или varArgs(boolean...). Все варианты допустимы. Поэтому вызов принципиально неоднозначен.

Если же мы закомментируем допустим описание методов для double… и boolean… , а так же их вызовы и оставим только описание метода int… и его вызов с параметрами и без то все откомпилируется и будет работать нормально.

A00035

В данном случае программа сгенерирует следующий вывод:

A00036

Рассмотрим еще один пример неопределенности. Следующие перегруженные версии метода varArgs()  изначально неоднозначны, несмотря на то, что одна из них принимает обычный параметр:

static void varArgs(int ... v)
static void varArgs(int n, int ... v)

Хотя списки параметров метода varArgs()  различны, компилятор не имеет возможности разрешения следующего вызова:

varArgs(1)

Должен ли он быть преобразован в обращение к varArgs(int...)  с одним аргументом переменной длины или в обращение к varArgs(int, int...)  без аргументов переменной длины? Компилятор не имеет возможности ответить на этот вопрос. Таким образом ситуация неоднозначна.

Из-за ошибок неопределенности, подобных описанным, в некоторых случаях придется пренебрегать перегрузкой и просто использовать два различных имени метода. Кроме того, в некоторых случаях ошибки неопределенности служат признаком концептуальных изъянов программы, которые можно устранить путем более тщательного построения решения задачи.

Ну и на последок небольшой пример программы, которая генерирует двумерный массив с переменной длиной строк, заполняет его числами и затем сортирует и выводит все это на консоль.

Программа может сгенерировать такой вывод:

A00028

За сим с массивами пока заканчиваем, но мы с ними еще не раз встретимся.

4 комментария:

  1. Большое спасибо за материал Надо печатную версию сделать Я бы купил

    ОтветитьУдалить
    Ответы
    1. Пожалуйста. Может когда и сделаю. Вот карантин еще продлится погода, можно и книгу написать :)

      Удалить
    2. Только вот меня с удаленки в офис перевели :(

      Удалить