28 мая 2015 г.

Переезд блога

Вот те на! К двадцатилетию Java, Goole без объявления войны поменял API доступа к своему сервису blogger.

В связи с этим у меня перестал работать (публиковать заметки) мой любимый редактор блогов Windows Live Writer :(

Поэтому блог переезжает на https://pr0java.wordpress.com/

Надеюсь Goole и Microsoft договорятся и Microsoft обновит свой редактор, чтобы можно было продолжать работать с blogger.



Мои предположения подтвердились. Вот что написали разработчики Google:

Today we ended support for ClientLogin OAuth 1 (3LO), AuthSub, and OpenID2, and started the shutdown process. Clients attempting to use these services will begin to fail and must be migrated to OAuth 2.0 and OpenID Connect.

26 мая 2015 г.

Java празднует двадцатилетие

http://www.3dnews.ru/914619

http://oracle.com.edgesuite.net/timeline/java/

oracle2505-2

oracle2505-3

Под руководством Oracle были выпущены два релиза платформы — Java 7 и Java 8, а на 2016 год запланирован выпуск Java 9. Ключевой особенностью девятой версии Java станет поддержка технологических решений проекта Jigsaw, которые обеспечат модульность платформы и её масштабируемость для обширного спектра устройств, а также позволят повысить безопасность и скорость работы создаваемых приложений. Кроме того, в Java 9 запланированы такие функциональные возможности, как Java Shell (интерактивный инструмент для оценки фрагментов Java-кода), новый API для HTTP-клиентов с поддержкой HTTP/2 и WebSockets, портирование на архитектуру ARM AArch64 под управлением Linux и множество обновлений существующих API, а также несколько значительных улучшений производительности.

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

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

18 мая 2015 г.

Массивы. Часть 3 – многомерные массивы.

В Java многомерные массивы представляют собой массивы массивов.

При объявлении переменной многомерного массива для указания каждого дополнительного индекса используют отдельный набор квадратных скобок. Например, следующий код объявляет переменную двумерного массива twoD.

int twoD[][] = new int[4][5];

Этот оператор распределяет память для массива размерностью 4×5 и присваивает ссылку на  него переменной twoD. Внутренне эта матрица реализована как массив массивов значений типа int. С точки зрения логической организации этот массив будет выглядеть так:

A00011

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

Допустим, вы хотите представить таблицу умножения в виде многомерного массива:

int[][] multiplicationTable;

Каждая пара квадратных скобок представляет одно измерение, поэтому данный массив является двухмерным. Чтобы получить доступ к одиночному элементу int двухмерного массива, нужно указать два значения индекса, по одному для каждого измерения. Если допустить, что данный массив был задан как таблица умножения, то значение int, находящееся в любом элементе, есть произведение этих индексов. Таким образом, значение products[2][4]  равно 8, а значением products[3][7]  будет 21.

Новый многомерный массив создается при помощи ключевого слова new с указанием размеров обоих измерений массива. Например:

int[][] multiplicationTable = new int[10][10];

В некоторых языках такой массив создается в виде единого блока из 100 значений int. Java поступает иначе. Эта строка кода выполняет три действия:

  • Объявляет переменную с именем multiplicationTable, которая содержит ссылку на массив ссылок, которые в свою очередь будут указывать на массивы int.
  • Создает массив из 10 элементов (первый индекс), который будет содержать ссылки на 10 одномерных массивов int. Вот от сюда собственно и понятие – массив массивов.
    На этой стадии создания массив ссылок заполняется значениями по умолчанию, то есть значениями null.
  • Создает еще 10 массивов, каждый из которых в свою очередь является массивом из 10 элементов int. Присваивает ссылки на каждый из этих 10 новых массивов элементам массива, созданного на втором шаге. По умолчанию каждый элемент int каждого из этих 10 новых массивов получает значение 0.

Другими словами, представленную выше строку кода, можно записать так:

int[][] multiplicationTable = new int[10][]; // первый индекс содержит ссылки на массивы int
  
for (int i = 0; i < 10; i++)
      
multiplicationTable[i] = new int[10]; // создаем 10 массивов int

Графически это можно изобразить так:

A00012

Очень важно понять, что каждый из массивов с элементами int, располагаются в памяти непрерывным куском, но где и как расположены каждый из них это определяет виртуальная машина java. Исходя из этого есть рекомендация, что наружные (левые) размерности массива лучше делать меньше, а самые больше размерности внутри (правее), поскольку это, во-первых, уменьшит фрагментацию памяти, а во вторых потребует гораздо меньше памяти для размещения массива. Возьмем к примеру вот такие два определения двумерных массивов:

int[][] a = new int[10][1000];
int[][] b = new int[1000][10];

В случае массива a, количество порождаемых в памяти объектов равно 11, а в случае массива b – 1001. Создание и обслуживание каждого объекта в памяти виртуальной машины имеет свои накладные расходы, так как виртуальная машина считает ссылки для каждого объекта, хранит его атрибуты и т.д. и т.п. Таким образом массив b может занимать в памяти в полтора, а то и в два раза больше места чем массив a.

Чтобы все еще стало понятней, лучше немного попрактиковаться Smile

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

A00013

По существу шаги 1 и 2 выполняются в 9 строке программы.

Строки 12-15 лишь выводят значения элементов первого индекса массива, где содержаться указатели на массивы значений int. Но на данный момент там еще значения null, так как им еще не были присвоены ссылки на массивы со значениями int.

После создания массивов int (строки 18-21), на третьем шаге мы видим что теперь в первом индексе, то есть в массиве содержащем ссылки на массивы int уже находятся ссылки на эти массивы.

И теперь если обратиться по обоим индексам массива multiplicationTable, то мы увидим что массивы int были заполнены значениями по умолчанию для данного типа, то есть нулями.

Ключевое слово new автоматически выполняет инициализацию элементов массива значениями по умолчанию.

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

При работе с многомерными массивами в Java очень важно понять, что первые индексы многомерных массивов содержат только массивы ссылок, и только последний (самый правый) индекс содержит непосредственно элемент данных типа объявленного для массива.

То есть в Java можно объявить и более чем двумерные массивы. Например:

int[][][] dim3D;

И тут очень важно понимать, что первый индекс данного массива, содержит массив ссылок, на второй индекс данного массива, который в свою очередь тоже содержит массив ссылок на массивы значений int.

То есть если в данном случае мы попробуем вывести на консоль значение int[1][1], то мы получим адрес ссылки, то есть примерно то же, что и на шаге 3 в предыдущем примере. И только по полному индексу int[1][1][1] мы сможем получить значение элемента массива типа int.

Если так же рассмотреть предыдущий пример, то код приведенный ниже вызовет ошибку компиляции:

int[][] multiplicationTable = new int[10][];
multiplicationTable[0] = 10; // ошибка компиляции

Не смотря на то что 10 является значением int, данный код не будет скомпилирован и будет выдана ошибка: cannot convert from int to int[]. Это произошло потому, что ожидается, что данный элемент массива будет хранить ссылку на объект, а не сам объект. Строка 12 в следующем примере, как раз и демонстрирует этот момент, как сделать чтобы не было ошибки.

В тоже время нижеприведенный код не вызовет ошибки компиляции, но вызовет ошибку во время исполнения: NullPointerException.

int[][] multiplicationTable = new int[10][];
multiplicationTable[0][0] = 10; // ошибка во время исполнения

Это происходит потому, что не был создан объект, в данном случае массив int-ов. То есть мы создали массив хранящий ссылки, на массивы int-ов, но сами эти массивы мы еще не создали, поэтому обращение к несуществующему объекту вызывает данную ошибку. Строка 19 из предыдущего примера, как раз показывает пример правильного создания объектов (массивов), на которые ссылается первый индекс. Ключевое слово new, так же служит и для создания и инициализации вложенных массивов.

В последнем примере, мы создали массив ссылок, но не создали массив int-ов, поэтому к нему и не возможно обратиться. Это можно исправить следующим кодом:

int[][] multiplicationTable = new int[10][];
multiplicationTable[0] = new int [10];
multiplicationTable[0][0] = 10; // нет ошибки

Все правила создания массивов, которые мы рассматривали ранее, справедливы и для много мерных массивов, то есть должны присутствовать все стадии: объявление, создание и инициализация.

Как уже упоминалось в Java можно создавать массивы любой размерности:

float[][][] globalTemperatureData = new float[360][180][100];

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

float[][][] globalTemperatureData = new float[360][][];
float[][][] globalTemperatureData = new float[360][180][];

Но такие варианты ошибочны:

float[][][] globalTemperatureData = new float[360][][100]; // Ошибка!
float[][][] globalTemperatureData = new float[][180][100]; // Ошибка!

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

Подобно одномерным массивам, многомерные массивы можно инициализировать при помощи массива-литерала. Нужно просто вложить массивы в другие массивы, применяя множество вложенных фигурных скобок. Пример ниже создает массив products[5][5]:

int[][] products = { { 0, 0, 0, 0, 0 },
                            
{ 0, 1, 2, 3, 4 },
                            
{ 0, 2, 4, 6, 8 },
                            
{ 0, 3, 6, 9, 12 },
                            
{ 0, 4, 8, 12, 16 } };

Напомню, что такой синтаксис инициализации, можно использовать только при объявлении массива или при использовании анонимного массива, то есть массива без имени:

boolean response = bilingualQuestion( question, new String[][] {{ "Yes", "No" },{ "Oui", "Non" } } );

Поскольку в Java многомерные массивы реализуются как массивы массивов,  вы можете использовать не только прямоугольные массвы. Например:

int[][] triangle = {{1, 2, 3, 4, 5},
                         
{6, 7, 8, 9},
                         
{10, 11, 12},
                         
{13, 14},
                         
{15}};

Приведу еще одни пример небольшой магии с массивами в Java, чтобы углубить понимание данной темы.

A00014

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

A00015

В строке 9 мы создаем и инициализируем одномерный массив. В 11 строке создаем двумерный массив. В 12 строке происходит магия. Если вы внимательно читали, все что было написано до сих пор то должны разобраться что произошло.

Поскольку, как я уже говорил, первые индексы многомерных массивов содержат ссылки на массивы, то в данном случае мы туда присвоили ссылку на одномерный массив oneD.

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

А пока добавлю еще немножко магии к предыдущей программе Smile

A00016

Тут я добавил в строках 20 и 21 поясняющий вывод, что же сталось с массивами и как они выглядят после операции в строке 17.

Кроме этого были добавлены строки создания массива int-ов, для второго индекса массива twoD, то есть для индекса 1, а так же заполнения его значениями. Это строки 23-25.

Далее, строка 26, просто вывод массива twoD на консоль.

В 27 строке небольшая магия, мы присвоили ссылку на массив int-ов, которые только что создали в массиве twoD, массиву oneD.

Затем вывели содержимое массива oneD на консоль. И уже после этого адреса ссылок на массивы.

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

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

A00017

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

A00018

Это пример работы с треугольным или ступенчатым массивом. На самом деле массивы int-ов могут быть произвольной длинны.

В 16 строке мы воспользовались стандартной библиотечным методом (Arrays.deepToString) SDK для вывода многомерных массивов на консоль, но как видно из вывода, данный метод выводит любой многомерный массив в строку, разделяя его размерности квадратными скобкам, что не очень наглядно.

Затем в цикле (строки 18-23) мы выводим треугольный массив более наглядным образом.

A00019

 

Следующий пример чуть интересней, будем превращать прямоугольный массив в треугольный.

A00020В данной программе мы изначально имеем три массива: один одномерный и два двумерных, но с разным количеством элементов в строках – 2 и 3 соответственно.

Как видно на примере вывода данной программы справа, сперва мы выводим все три массива в их изначальном состоянии.

В строках 31 и 32 мы колдуем с массивом three и затем выводим результат нашего шаманства на консоль.

В результате, как-бы, получаем честный треугольный массив.

Как-бы, это потому, что на самом деле последние два индекса ссылаются массивы two и one.

А честный он потому, что нам придется с ним работать уже как с треугольным, так как, например, после магии мы уже не сможем обратиться к индексу three[2][2], поскольку получим ошибку во время исполнения программы ArrayIndexOutOfBoundsException. А как-бы он потому, что работая с индексами three[1] и three[2] мы на самом деле будем работать с массивами two и one соответственно.

Теперь рассмотрим простенький пример трехмерного массива. Следующая программа создает трехмерный массив размерности 3×4×5. Затем она инициализирует каждый элемент произведением его индексов и выводит все это дело на консоль.

A00021

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

A00022

Вот такое у нас 3D Smile

 

Ну и в завершение данного поста приведу программу сортировки двумерного массива пузырьком.

Первый алгоритм, это классика жанра, сортировка обычным пузырьком, второй алгоритм я написал забавы ради, что-бы показать как модифицированный алгоритм может повлиять на производительность и скорость вычислений.

Данная программа может принимать аргументы из командной строки и создавать двумерные массивы заданной размерности. Если аргументы не переданы то создается массив 10х10. Затем он заполняется случайными числами, копируется в массив который будет отсортирован первым алгоритмом, сортируется, выводится на консоль, затем несортированный снова копируется для сортировки, сортируется вторым алгоритмом и выводится на консоль.

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

A00009

A00010

Тут приведены примеры запусков без аргументов и с аргументами из командной строки.

Как видите разница между количеством итераций и перестановок в алгоритмах различается от двух и более раз.

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

Объяснять отличие работы второго алгоритма немного ленно. Постарайтесь разобраться сами.

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

В следующем посте разберем некоторые методы работы с массивами из стандартной библиотеки.

7 мая 2015 г.

Массивы. Часть 2 – одномерные массивы.

Одномерные массивы, по сути, представляют собой список однотипных переменных. Все! На этом с теорией в данном разделе все! Далее только практика, так как материал достаточно простой и вся теория по нему была в предыдущем посте.

Теперь к делу! Рассмотрим простой алгоритм сортировки массива целых чисел по возрастанию:

Вывод у этой программы может быть таким:

A00003

А теперь немного изменим этот алгоритм и сравним его производительность с первым:

Вывод этой программы может быть таким:

A00004

Как видите разница в скорости сортировки может быть весьма значительная. Правда это зависит от количества чисел и их расположения в несортированном массиве.

В другой раз вывод может быть таким:

A00005

Суть этой программы такова. Мы создаем массив intOrigin в котором храним не сортированный массив, затем копируем этот массив в массив intArray, который затем сортируем первым алгоритмом, и после сортировки, снова копируем не сортированный массив в intArray и опять его сортируем вторым алгоритмом. В обоих алгоритмах считаем количество итераций. Как видим в первом алгоритме оно постоянно, во втором же оно меняется. В целом, второй алгоритм более эффективен чем первый.

Ну и для примера рассмотрим еще один алгоритм сортировки называемый сортировкой выбором. Суть алгоритма такова. Во всём отыскиваем минимальный элемент, меняем его местами с начальным. Затем в оставшейся части массива (т. е. среди всех элементов кроме начального) снова отыскиваем минимальный элемент, меняем его местами уже со вторым элементом в массиве. И так далее.

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

A00006

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

Следующая программа более простая. Она демонстрирует динамическое создание и инициализацию массива значениями:

A00007Массив строк str создается только в случае если в командной строке передано два или больше аргументов и инициализируется значениями первых двух аргументов.

В данном случае длина массива известна, но будет ли создан сам массив str не известно, так как это зависит от переданных аргументов.

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

Если переданные аргументы являются только строками, то массив intArray создан не будет. Чтобы он создался, необходимо первым параметром передать целое число. Длина созданного массива будет равна этому числу. После этого массив заполняется случайными значениями и выводится.

Вывод этой программы может быть таким:

A00008

6 мая 2015 г.

Массивы. Часть 1 – Введение.

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

Массивы относятся к ссылочным типам данных, собственно как и все остальные типы, кроме примитивных. Напомню еще раз, что в Java все является объектом, исключение составляют лишь примитивные типы.

Массивы могут быть одномерными и многомерными.

Процесс создания массива можно разделить на три этапа:

  • Объявление (declaration)
  • Создание (instantation)
  • Инициализация  (initialization)

Объявление (declaration) массива

На этом этапе определяется только переменная типа ссылка (reference) на массив, содержащая тип массива. Для этого записывается имя типа элементов массива, квадратными скобками указывается, что объявляется ссылка на массив, а не простая переменная, и перечисляются имена переменных ссылочного типа, например:

int[] numbers; // numbers ссылка на массив int-ов
String[] str; // str ссылка на массив строк
byte[][] twoBytes; // twoBytes ссылка на двумерный массив байтов
char[] letters, digits; //letters и digits ссылки на массивы символов

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

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

byte arrayOfBytes[]; // То же, что и byte[] arrayOfBytes
byte arrayOfArrayOfBytes[][]; // То же, что и byte[][] arrayOfArrayOfBytes
byte[] arrayOfArrayOfBytes[]; // То же, что и byte[][] arrayOfArrayOfBytes

Однако зачастую такой синтаксис сбивает с толку, поэтому его следует избегать. В следующем примере, легко спутать что имелось в виду:

float rates[], maxRate; // может хотели объявить два массива?

Такой синтаксис не рекомендуется использовать, так как уже говорилось, что он сбивает  с толку, кроме того, в соглашения по оформлению Java кода, рекомендуется синтаксис, который был приведен первым, то есть квадратные скобки следуют сразу за типом объявляемого массива.

В данном случае объявлены массив значений типа float с именем rates и переменная типа float – maxRate.

То есть, если квадратные скобки стоят сразу после типа объявляемого массива, то все переменные которые объявлены в этой строке являются ссылками на массивы объявляемого типа, а если же скобки стоят справа от переменной, то только она является ссылкой на массив объявленного типа.

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

Пока объявленная переменная массива не определена, она может содержать (если вы присвоите) значение null. И только после определения она будет содержать ссылку на конкретный объект.

Указать длину массива при объявлении переменной массива невозможно, поскольку размер является строго функцией объекта массива, а не ссылки на него.

Важно понимать разницу между ссылкой на массив (имя переменной массива) и самим массивом, то есть объектом, на который указывает эта ссылка.

Создание (instantation) массива

На этом этапе указывается количество элементов массива, называемое его размером, выделяется место для массива в оперативной памяти, переменной-ссылке присваивается оператором = адрес массива. Все эти действия производятся оператором new за которым следует тип элементов массива. Например:

letters = new char[10]; // создали массив char-ов размеров в 10 элементов

Но стоит еще раз заметить, что до этого переменная letters, должна быть объявлена как массив. Чтобы было более понятно, это можно представить вот так:

char[] letters; // объявили letters как ссылку на массив символов char
letters = new char[10]; // создали массив char-ов размеров в 10 элементов

При создании массива с таким синтаксисом все элементы массива автоматически инициализируются значениями по умолчанию. Это false для значений boolean, '\u0000'  для значений char, 0 для целых значений, 0.0 для значений с плавающей точкой и null для объектов или массивов.

В Java размер массива фиксирован. Созданный массив нельзя увеличить или уменьшить. Желаемый размер создаваемого массива задается неотрицательным целым числом. Но в любое время переменной типа массива может быть сопоставлен новый массив другого размера. То есть может быть присвоена ссылка на другой массив того же типа что и объявленная переменная.

Индексы массивов всегда начинаются с 0.

Первые две операции: объявление и создание массива можно объединить в один оператор. Например:

char[] letters = new char[10];

Этот оператор эквивалентен двум приведенным выше.

После данной операции переменная letters будет уже содержать ссылку на массив и если попробовать вывести ее значение то мы получим значение, что то вроде [C@659e0bfd. А все элементы массива, как уже говорилось будут содержать значения по умолчанию для объявленного типа.

Создать массив можно только при помощи оператора new, но ссылку на уже существующий массив можно присвоить другой ссылке того же типа. Например:

int[] a = new int[4];
int[] b =a;

Но надо иметь в виду, что переменные a и b указывают на один и тот же массив. По началу это может сбивать с толку, но если помнить что мы имеем дело с ссылочными типами данных, то все становится на свои места. Если этот момент не понятен, то чуть позже мы все это разберем на примерах.

Следует, так же, еще раз упомянуть, что ссылке (переменной массива) можно присвоить "пустое" значение null, не указывающее ни на какой адрес оперативной памяти:

a=null;

После этого массив, на который указывала данная ссылка, теряется, если на него не было других ссылок.

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

Можно создавать и использовать массивы нулевой длины (пустой массив). Например:

boolean[] bits = new boolean[0];

Инициализировать такой массив нельзя, так как у него просто нет элементов которые можно инициализировать. Сразу же возникает вопрос, а на кой ляд они тогда вообще нужны эти пустые массивы? Но они нужны и даже очень полезны!

Пустой массив принято использовать в тех местах программы, где заранее неизвестно, будут элементы или нет. Если элементы будут, то возвращается непустой массив, если элементов нет - пустой массив. Примером может служить массив строк который передается в метод main() и содержит аргументы командной строки, а если их нет, то возвращается пустой массив.

Пустой массив лучше, чем null, потому что не требует отдельного if'а для обработки. То же верно для списков и других коллекций. Именно поэтому существуют методы Collections.emptyList, emptySet, emptyMap.

Инициализация (initialization) массива

На этом этапе элементы массива получают начальные значения. Инициализировать элементы массива значениями можно несколькими способами:

  1. Присвоить каждому элементу массива конкретное значение (это можно сделать например в цикле, но до этого массив уже должен быть объявлен и создан)
  2. Инициализировать массив при помощи перечисления значений его элементов в фигурных скобках (это можно сделать как на этапе объявления, так и на этапе создания, но синтаксис при этом разный)

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

Индексы можно задавать любыми целочисленными выражениями, кроме типа long, например a[i+j] , a[i%5] , a[++i] . Исполняющая система Java следит за тем, чтобы значения этих выражений не выходили за границы длины массива. Если же выход все же произойдет интерпретатор Java в таком случае прекратит выполнение программы и выведет на консоль сообщение о выходе индекса массива за границы его определения (ArrayIndexOutOfBoundsException).

Рассмотрим пример первого способа инициализации:

int[] ar = new int[2];
ar[0]=1;
ar[1]=2;

Второй способ инициализации можно реализовать по разному.

Инициализацию массива можно совместить с этапом создания, но до этой операции массив уже должен быть объявлен. Например:

int[] ar; // объявление массива
ar = new int[]{1,2}; // создание и инициализация

До создания и инициализации массива ar он уже был объявлен.

Так же инициализировать массив можно на этапе его объявления следующим синтаксисом:

int[] ar = {1,2}; // объявление, создание и инициализация массива

Внимание! Этот синтаксис инициализации массива работает только при объявлении массива и совмещает сразу все три операции объявление, создание и инициализацию. Если массив уже объявлен, то такой синтаксис использовать нельзя. Компилятор выдаст ошибку. То есть:

int[] ar; // объявление массива
ar = {1,2}; // ОШИБКА!!! создание и инициализация массива

Такое действо не прокатит.

Так же можно инициализировать на этапе объявления и чуть чуть по другому:

int[] ar = new int[]{1,2}; // объявление, создание и инициализация

Хотя этот синтаксис более длинный. Если вы заметили, то данный синтаксис это тоже совмещение всех трех операций: объявления, создания и инициализации.

В Java предусмотрен синтаксис, который поддерживает анонимные массивы (они не присваиваются переменным и, следовательно, у них нет имен). Иногда массив нужно задействовать лишь один раз (например, передать его методу), следовательно, вы не хотите тратить время на присваивание его переменной, поэтому можно сразу же использовать результат оператора new. Например:

System.out.println(new char[] { 'H', 'e', 'l', 'l', 'o' });

Синтаксис инициализации массивов с помощью фигурных скобок называется литеральным, поскольку для инициализации используется массив-литерал.

Важно понимать что массивы-литералы создаются и инициализируются во время выполнения программы, а не во время ее компиляции. Рассмотрим следующий массив-литерал:

int[] perfectNumbers = {6, 28};

Он компилируется в такой байт-код Java:

int[] perfectNumbers = new int[2];
perfectNumbers[0] = 6;
perfectNumbers[1] = 28;

Поэтому если вам нужно разместить в Java программе много данных, лучше не включать их непосредственно в массив, поскольку компилятору Java придется создавать много байткодов инициализации массива, а затем интерпретатору Java нужно будет кропотливо выполнять весь этот код. В таких случаях лучше сохранять данные во внешнем файле и считывать их в программу во время ее выполнения.

Однако тот факт, что Java инициализирует массив во время выполнения программы, имеет важные последствия. Это означает, что элементы массива-литерала являются произвольными выражениями, вычисляемыми во время выполнения программы, а не постоянными выражениями, вычисляемыми компилятором. Например:

Point[] points = { circle1.getCenterPoint(), circle2.getCenterPoint() };

Теперь немножко попрактикуемся.

A00001

В хорошо нам известном методе main(), как раз и используется возможность возврата массива нулевой длины если в командной строке нет аргументов, что позволяет избежать использования оператора if для проверки на null, дабы избежать ошибки во время исполнения программы.

То есть мы сразу можем использовать массив в цикле, правда соблюдая правила чтобы не выйти за пределы максимального индекса.

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

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

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

A00002

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

Во втором случае мы передали аргументы в командной строке и следовательно массив был обработан в циклах.