Будет рассмотрен базовый функционал языка, то есть основная встроенная функциональность, такая как математические операторы, условия, циклы, бинарные операторы. Далее способы хранения и представления данных в Java, и в конце способы манипуляции данными, то есть функции (в терминах языка называющиеся методами).
\begin{itemize}
\item\nom{Метод}{функция в языке программирования, принадлежащая классу};
\item\nom{Переполнение}{целочи́сленное переполне́ние (англ. integer overflow) — ситуация в компьютерной арифметике, при которой вычисленное в результате операции значение не может быть помещено в тип данных};
\item\nom{Идентификатор}{идентификатор переменной - название переменной, по которому возможно получить доступ к области памяти, соответствующей этой переменной};
Относительно типизации языки программирования бывают типизированными и нетипизированными (бестиповыми). Нетипизированные языки не представляют большого интереса в современном программировании.
Отсутствие типизации в основном присуще чрезвычайно старым и низкоуровневым языкам программирования, например, Forth и некоторым ассемблерам. Все данные в таких языках считаются цепочками бит произвольной длины и не делятся на типы. Работа с ними часто труднее, при этом часто безтиповые языки работают быстрее типизированных, но описывать с их помощью большие проекты со сложными взаимосвязями довольно утомительно.
\item Статическая - у каждой переменной должен быть тип, и этот тип изменить нельзя. Этому свойству противопоставляется динамическая типизация;
\item Явная - при создании переменной ей обязательно необходимо присвоить какой-то тип, явно написав это в коде. В более поздних версиях языка (с 9й) стало возможным инициализировать переменные типа \code{var}, обозначающий нужный тип тогда, когда его возможно однозначно вывести из значения справа. Бывают языки с неявной типизацией, например, Python;
\item Строгая(сильная) - невозможно смешивать разнотипные данные. С другой стороны, существует JavaScript, в котором запись \code{2 + true} выдаст результат \code{3}.
Почти во всех примерах, которые используются для обучения, можно увидеть так называемый антипаттерн - плохой стиль для написания кода. Числа, которые находятся справа от оператора присваивания используются в коде без пояснений. Такой антипаттерн называется «магическое число». Магическое, потому что непонятно, что это за число, почему это число именно такое и что будет, если это число изменить.
Так лучше не делать. Заранее нужно сказать, что рекомендуется помещать все числа в коде в именованные константы, которые хранятся в начале файла. Плюсом такого подхода является возможность легко корректировать значения переменных в достаточно больших проектах.
Например, в вашем коде несколько тысяч строк, а какое-то число, скажем, возраст совершеннолетия, число 18, использовалось несколько десятков раз. При использовании приложения в стране, где совершеннолетием считается $21$ год вы должны будете перечитывать весь код в поисках магических «18» и исправить их на «21». В этом вопросе будет также важно не запутаться, действительно ли это $18$, которые означают совершеннолетие, а не количество карманов в жилетке Анатолия Вассермана\footnote{мы то знаем, что их 26}.
Все данные в Java делятся на две основные категории: примитивные и ссылочные. Таблица \hrf{tab:types} демонстрирует все восемь примитивных типов языка и их размерности. Чтобы отправить на хранение какие-то данные используется оператор присваивания. Присваивание в программировании - это не тоже самое, что математическое равенство, демонстрирующее тождественность, а полноценная операция.
Все присваивания всегда происходят справа налево, то есть сначала вычисляется правая часть, а потом результат вычислений присваивается левой. Исключений нет, именно поэтому в левой части не может быть никаких вычислений.
Шесть из восьми типов имеет диапазон значений, а значит основное их отличие в объёме занимаемой памяти. У\code{double} и \code{float} тоже есть диапазоны, но они заключаются в точности представления дробной части. Диапазоны означают, что если попытаться положить в переменную меньшего типа большее значение, произойдёт «переполнение переменной».
Чем именно чревато переполнение переменной легче показать на примере (по ссылке - \href{https://habr.com/ru/company/pvs-studio/blog/306748/}{расследование} крушения ракеты из-за переполнения переменной)
Если создать переменную типа byte, диапазон которого от $[-128, +127]$, и присвоить этой переменной значение $200$ произойдёт переполнение, как если попытаться влить пакет молока в напёрсток.
Важным вопросом при переполнении остаётся следующий: какое в переполненной переменной останется значение? Максимальное, $127$? $200-127=73$? Какой-то мусор? Каждый язык, а зачастую и разные компиляторы одного языка ведут себя в этом вопросе по разному.
\begin{frm}
\begin{center}
\exclВ современном мире гигагерцев и терабайтов почти никто не пользуется маленькими типами, но именно из-за этого ошибки переполнения переменных становятся опаснее испанской инквизиции.
После разговора о переполнении, нельзя не сказать о том, что именно переполняется. Далее будут представлены сведения которые касаются не только языка Java но и любого другого языка программирования. Эти сведения помогут разобраться в деталях того как хранится значение переменной в программе и как, в целом, происходит работа компьютерной техники.
\begin{frm}\infoВсе современные компьютеры, так или иначе работают от электричества и являются примитивными по своей сути устройствами, которые понимают только два состояния: есть напряжение в электрической цепи или нет. Эти два состояния принято записывать в виде 1 и 0, соответственно. \end{frm}
Все данные в любой программе - это единицы и нули. Данные в программе на Java не исключение, удобнее всего это явление рассматривать на примере примитивных данных. Поскольку в компьютере можно оперировать только двумя значениями то естественным образом используется двоичная система счисления.
Двоичная система счисления это система счисления с основанием два. Существуют и другие системы счисления, например, восьмеричная, но сейчас она отходит на второй план полностью уступая своё место шестнадцатеричной системе счисления. Каждая цифра в десятичной записи числа называется разрядом, аналогично в двоичной записи чисел каждая цифра тоже называется разрядом, но для компьютерной техники этот разряд называется битом.
Биты принято собирать в группы по восемь штук, по восемь разрядов, эти группы называются \textbf{байт}. В языке Java возможно оперировать минимальной единицей информации, такой как байт для этого есть соответствующий тип. Диапазон байта, согласно таблицы $[-128, +127]$, то есть байт информации может в себе содержать ровно 256 значений. Само число $127$ в двоичной записи это семиразрядное число, все разряды которого единицы (то есть байт выглядит как \code{01111111}). Последний, восьмой, самый старший бит, определяет знак числа\footnote{Здесь можно начать долгий и скучный разговор о схемотехнике и хранении отрицательных чисел с применением техники дополнительного кода.}. Достаточно знать формулу расчёта записи отрицательных значений:
Числа б\'{о}льших разрядностей могут хранить б\'{о}льшие значения, теперь преобразование диапазонов из десятичной системы счисления в двоичную покажет что \code{byte} это один байт, \code{short} это два байта, то есть 16 бит, \code{int} это 4 байта то есть 32 бита, а\code{long} это 8 байт или 64 бита хранения информации.
Значения в целочисленных типах могут быть только целые, никак и никогда невозможно присвоить им дробных значений. Про эти типы следует помнить следующее:
Как \code{int} преобразуется в меньше типы? Если написать цифрами справа число, которое может поместиться в переменную меньшего типа слева, то статический анализатор кода его пропустит, а компилятор преобразует в меньший тип автоматически (строка 9 на рис. \hrf{pic:byte-overflow}).
Как видно, к маленькому \code{byte} успешно присваивается \code{int}. Если же написать число которое больше типа слева и, соответственно, поместиться не может, среда разработки выдает предупреждение компилятора, что ожидался \code{byte}, а передан \code{int} (строка 10 рис \hrf{pic:byte-overflow}).
Часто нужно записать в виде числа какое-то значение большее чем может принимать \code{int}, и явно присвоить начальное значение переменной типа \code{long}.
В примере на рис. \hrf{pic:int-overflow} показана попытка присвоить значение 5000000000 переменной типа \code{long}. Из текста ошибки ясно, что невозможно положить такое большое значение в переменную типа \code{int}, а это значит, что справа \code{int}. Почему большой \code{int} без проблем присваивается к маленькому байту?
На рис. \hrf{pic:overflow-solution} продемонстрировано, что аналогичная ситуация возникает с типами \code{float} и \code{double}. Все дробные числа, написанные в коде - это \code{double}, поэтому положить их во \code{float} без дополнительных усилий невозможно. В этих случаях к написанному справа числу нужно добавить явное указание на его тип. Для \code{long} пишем \code{L}, а для \code{float} - \code{f}. Чаще всего \code{L} пишут заглавную, чтобы подчеркнуть, что тип больше, а\code{f} пишут маленькую, чтобы подчеркнуть, что мы уменьшаем тип. Но регистр в этом конкретном случае значения не имеет, можно писать и так и так.
Как видно из таблицы \hrf{tab:types}, два из восьми типов не имеют диапазонов значений. Это связано с тем, что диапазоны значений флоута и дабла заключаются не в величине возможных хранимых чисел, а в точности этих чисел после запятой.
\begin{frm}\info Числа с плавающей запятой в англоязычной литературе называются числа с плавающей точкой (от англ. floating point). Такое различие связано с тем, что в русскоязычной литературе принято отделять дробную часть числа запятой, а в европейской и американской - точкой. \end{frm}
Хранение чисел с плавающей запятой\footnote{хорошо и подробно, но на С, в посте на \href{https://habr.com/ru/post/112953/}{Хабре}.} работает по стандарту IEEE 754 (1985 г). Для работы с числами с плавающей запятой на аппаратурном уровне к обычному процессору добавляют математический сопроцессор (FPU, floating point unit).
Рисунок \hrf{pic:float-struct} демонстрирует, как распределяются биты в числах с плавающей запятой разных разрядностей, где S - Sign (знак), E - Exponent (8(11) разрядов поля порядка, экспонента), M - Mantissa (23(52) бита мантиссы, дробная часть числа).
Если попытаться уложить весь стандарт в два предложения, то получится примерно следующее: получить число в соответствующих разрядностях возможно по формулам:
Возьмём для примера число $-0,15625$, чтобы понять как его записывать, откинем знак, это будет единица в разряде, отвечающем за знак, и посчитаем мантиссу с порядком. Представим число как положительное и будем от него последовательно отнимать числа, являющиеся отрицательными степенями двойки, чтобы получить максимально близкое к нулю значение.
Очевидно, что $-1$ и $-2$ степени отнять не получится, поскольку мы явно уходим за границу нуля, а вот $-3$ прекрасно отнимается, значит порядок будет $127-3=124$, осталось понять, что получится в мантиссе.
Видим, что оставшееся после первого вычитания ($0,15625-0,125$) число - это $2^{-5}$. Значит в мантиссе пишем \code{01} и остальные нули, то есть слева направо указываем, какие степени после $-3$ будут нужны. $-4$ не нужна, а$-5$ нужна.
Так число с плавающей запятой возможно посчитать двумя способами: по приведённой формуле, или последовательно складывая разряды мантиссы умноженные на двойку в степени порядка, уменьшая порядок на каждом шагу.
\begin{figure}[h]
\centering
\includegraphics[width=17cm]{jc-02-float02.png}
\caption{Особенности работы с числами с плавающей запятой}
\label{pic:float-points}
\end{figure}
К особенностям работы чисел с плавающей запятой можно отнести:
\begin{itemize}
\item возможен как положительный, так и отрицательный ноль (в целых числах ноль всегда положительный);
\item есть огромная зона, отмеченная на рисунке \hrf{pic:float-points}, которая являет собой непредставимые числа, слишком большие для хранения внутри такой переменной или настолько маленькие, что мнимая единица в мантиссе отсутствует;
\item в таком числе можно хранить значения положительной и отрицательной бесконечности;
\item при работе с такими числами появляется понятие не-числа, при этом важно помнить, что \code{NaN != NaN}.
Шесть из восьми примитивных типов могут иметь как положительные, так и отрицательные значения, они называются \textbf{«знаковые»} типы. В таблице есть два типа, у которых есть диапазон но нет отрицательных значений, это \code{boolean} и \code{char}
Булев тип хранит значение \code{true} или \code{false}. На собеседованиях иногда спрашивают, сколько места занимает \code{boolean}. В Java объём хранения не определён и зависит от конкретной JVM, обычно считают, что это один байт.
\begin{table}[h!]
\centering
\begin{tabular}{||c|c|c||c|c|c||c|c|c||c|c|c||}
\hline
dec & hex & val & dec & hex & val & dec & hex & val & dec & hex & val \\
Тип \code{char} единственный беззнаковый целочисленный тип в языке, то есть его старший разряд хранит полезное значение, а не признак положительности. Тип целочисленный но по умолчанию среда исполнения интерпретирует его как символ по таблице utf-8 (см фрагмент в таблице \hrf{table:utf-8-ascii}). В языке Java есть разница между одинарными и двойными кавычками. В одинарных кавычках всегда записывается символ, который на самом деле является целочисленным значением, а в двойных кавычках всегда записывается строка, которая фактически является экземпляром класса \code{String}. Поскольку типизация строгая, то невозможно записать в \code{char} строки, а в строки числа.
Далее в любой момент можно \textit{присвоить} этой переменной значение, то есть необходимо написать идентификатор использовать оператор присваивания и справа написать значение, которое вы хотите присвоить данной переменной, поставить в конце строки точку с запятой.
Java - это язык со строгой статической типизацией, но преобразование типов в ней всё равно есть. Простыми словами, преобразование типов - это когда компилятор видит, что типы переменных по разные стороны присваивания разные, начинает разрешать это противоречие. Преобразование типов бывает явное и неявное.
\infoВ разговоре или в сообществах можно услышать или прочитать термины тайпкастинг, кастинг, каст, кастануть, и другие производные от английского typecasting.
Неявное преобразование типов происходит, когда присваиваются числа переменным меньшей размерности, чем \code{int}. Число справа это \code{int}, а значит 32 разряда, а слева, например, \code{byte}, и в нём всего 8 разрядов, но ни среда ни компилятор не поругались, потому что значение в большом \code{int} не превысило 8 разрядов маленького \code{byte}. Итак неявное преобразование типов происходит в случаях, когда, «всё и так понятно». В случае, если неявное преобразование невозможно, статический анализатор кода выдаёт ошибку, что ожидался один тип, а был дан другой.
Явное преобразование типов происходит, когда мы явно пишем в коде, что некоторое значение должно иметь определённый тип. Этот вариант приведения типов тоже был рассмотрен, когда к числам дописывались типовые квалификаторы \code{L} и \code{f}. Но чаще всего случается, что происходит присваивание переменным не тех значений, которые были написаны в тексте программы, а те, которые получились в результате каких-то вычислений.
На рис. \hrf{pic:byte-cast-error} приведён простейший пример, в котором очевидно, что внутри переменной \code{i0} содержится значение, не превышающее одного байта хранения, а значит возможно явно сообщить компилятору, что значение точно поместится в \code{byte}. \textit{Явно преобразовать типы}. Для этого нужно в правой части оператора присваивания перед идентификатором переменной в скобках добавить название типа, к которому необходимо преобразовать значение этой переменной.
Constare - (лат. стоять твёрдо). Константность это свойство неизменяемости. В Java ключевое слово \code{const} не реализовано, хоть и входит в список ключевых, зарезервированных. Константы создаются при помощи ключевого слова \code{final}. Ключевое слово файнал возможно применять не только с примитивами, но и со ссылочными типами, методами, классами.
Ссылочные типы данных - это все типы данных, кроме восьми перечисленных примитивных. Самым простым из ссылочных типов является массив. Фактически массив выведен на уровень языка и не имеет специального ключевого слова.
Ссылочные типы отличаются от примитивных местом хранения информации. В примитивах данные хранятся там, где существует переменная и где написан её идентификатор, а по идентификатору ссылочного типа хранится не значение, а ссылка. Ссылку можно представить как ярлык на рабочем столе, то есть очевидно, что непосредственная информация хранится не там, где написан идентификатор. Такое явное разделение идентификатора переменной и данных важно помнить и понимать при работе с ООП.
Самый младший индекс любого массива - ноль, поскольку \textbf{индекс} - это значение смещения по элементам относительно начального адреса массива. То есть, для получения самого первого элемента нужно сместиться на ноль шагов. Очевидно, что самый последний элемент в массиве из десяти значений, будет храниться по девятому индексу.
Массивы возможно создавать несколькими способами (листинг \hrf{lst:array-init}). В общем виде объявление - это тип, квадратные скобки как обозначение того, что это будет массив из переменных этого типа, идентификатор (строка \hrf{codeline:arr-define}). Инициализировать массив можно либо ссылкой на другой массив (строка \hrf{codeline:arr-link}), пустым массивом (строка \hrf{codeline:arr-new}) или заранее заданными значениями, записанными через запятую в фигурных скобках (строка \hrf{codeline:arr-values}). Присвоить в процессе работы идентификатору возможно только значение ссылки из другого идентификатора или новый пустой массив.
\begin{frm}\excl Никак и никогда нельзя присвоить идентификатору целый готовый массив в процессе работы, нельзя стандартными средствами переприсвоить ряд значений части массива (так называемые слайсы или срезы). \end{frm}
Массивы бывают как одномерные, так и многомерные. Многомерный массив - это всегда массив из массивов меньшего размера: двумерный массив - это массив одномерных, трёхмерный - массив двумерных и так далее. Правила инициализации у них не отличаются. Преобразовать тип массива нельзя никогда, но можно преобразовать тип каждого отдельного элемента при чтении. Это связано с тем, что под массивы сразу выделяется непрерывная область памяти, асо сменой типа всех значений массива эту область нужно будет или значительно расширять или значительно сужать.
Если логика программы предполагает создание нижних измерений массива в процессе работы программы, то при инициализации массива верхнего уровня не следует указывать размерности нижних уровней. Это связано с тем, что при инициализации, Java сразу выделяет память под все измерения, а присваивание нижним измерениям новых ссылок на создаваемые в процессе работы массивы, будет пересоздавать области памяти, получается небольшая утечка памяти.
Прочитать из массива значение возможно обратившись к ячейке массива по индексу. Записать в массив значение возможно обратившись к ячейке массива по индексу, и применив оператор присваивания.
В каждом объекте массива есть специальное поле (рис. \hrf{pic:array-length}), которое обозначает длину данного массива. Поле находится в классе \code{__Array__} и является публичной константой.
Математические операторы работают как и предполагается - складывают, вычитают, делят, умножают, делают это по приоритетам известным нам с пятого класса, а если приоритет одинаков - слева направо. Специального оператора возведения в степень как в пайтоне нет. Единственное, что следует помнить, что оператор присваивания продолжает быть оператором присваивания, а не является математическим равенством, а значит сначала посчитается всё, что слева, а потом результат попробует присвоиться переменной справа. Припоминаем что там за дела с целочисленным делением и отбрасыванием дробной части.
Условия представлены в языке привычными \code{if}, \code{else if}, \code{else}, «если», «иначе если», «в противном случае», которые являются единым оператором выбора, то есть если исполнение программы пошло по одной из веток, то в другую ветку условия программа точно не зайдёт. Каждая ветвь условного оператора - это отдельный кодовый блок со своим окружением и локальными переменными.
Существует альтернатива оператору \code{else if} - использование оператора \code{switch}, который позволяет осуществлять множественный выбор между числовыми значениями. У оператора есть ряд особенностей:
\item это оператор, состоящий из одного кодового блока, то есть сегменты кода находятся в одной области видимости. Если не использовать оператор \code{break}, есть риск «проваливаться» в следующие кейсы;
\item нельзя создать диапазон значений;
\item достаточно сложно создавать локальные переменные с одинаковым названием для каждого кейса.
Цикл - это набор повторяющихся до наступления условия действий. \code{while} - самый простой, чаще всего используется, когда нужно описать бесконечный цикл. \code{do-while} единственный цикл с постусловием, то есть сначала выполняется тело, а затем принимается решение о необходимости зацикливания, используется для ожидания ответов на запрос и возможного повторения запроса по условию. \code{for} - классический счётный цикл, его почему-то программисты любят больше всего.
Существует также активно пропагандируемый цикл - \code{foreach}, работает не совсем очевидным образом, для понимания его работы необходимо ознакомиться с ООП и понятием итератора.
В современных реалиях мегамощных компьютеров вряд ли кто-то задумывается об оптимизации скорости выполнения программы или экономии занимаемой памяти. Но всё меняется, когда программист впервые принимает сложное решение: запрограммировать микроконтроллер или другой «интернет вещей». Там в вашем распоряжении жалкие пара сотен килобайт памяти, если очень повезёт, в которые нужно не только как-то вложить текст программы и исполняемый бинарный код, но и какие-то промежуточные, пользовательские и другие данные, буферы обмена и обработки. Другая ситуация, в которой нужно начинать «думать о занимаемом пространстве» это разработка протоколов передачи данных, чтобы протокол был быстрый, не передавал по сети большие объёмы данных и быстро преобразовывался. На помощь приходит натуральная для информатики система счисления, двоичная.
Литеральные «и», «или», «не» уже знакомы по условным операторам. Литеральные операции применяются ко всему числовому литералу целиком, а не к каждому отдельному биту. Их особенность заключается в том, как язык программирования интерпретирует числа.
\begin{frm}\infoВ Java в литеральных операциях может участвовать только тип \code{boolean}, а C++ воспринимает любой ненулевой целочисленный литерал как истину, а нулевой, соответственно, как ложь.
Когда говорят о битовых операциях волей-неволей появляется необходимость поговорить о таблицах истинности. В таблице \hrf{table:bin-truth-tables} вы видите таблицы истинности для арифметических битовых операций. Битовые операции отличаются тем, что для неподготовленного взгляда они производят почти магические действия, потому что манипулируют двоичным представлением числа.
С битовыми сдвигами работать гораздо интереснее и выгоднее. Они производят арифметический сдвиг значения слева на количество разрядов, указанное справа, в таблице \hrf{table:bit-shift} представлены числа, в битовом представлении это одна единственная единица, находящаяся в разных разрядах числа. Это демонстрация сдвига на один разряд влево, и, как следствие, умножение на два. Обратная ситуация со сдвигом вправо, он является целочисленным делением.
У функций есть правила именования: функция - это переходный глагол совершенного вида в настоящем времени (вернуть, посчитать, установить, создать), часто снабжаемый дополнением, субъектом действия. Методы в Java пишутся lowerCamelCase. Важно, в каком порядке записаны параметры метода, от этого будет зависеть порядок передачи в неё аргументов. Методы обособлены и их параметры локальны, то есть не видны другим функциям.
Все аргументы передаются копированием, не важно, копирование это числовой константы, числового значения переменной или хранимой в переменной ссылке на массив. Сам объект в метод не копируется, а копируется только его ссылка.
Возвращаемые из методов значения появляются в том месте, где метод был вызван. Если будет вызвано несколько методов, то весь контекст исполнения первого метода сохраняется, кладётся (на стек) в стопку уже вызванных методов и процессор идёт выполнять только что вызванный второй метод. По завершении вызванного второго метода, мы снимаем со стека лежащий там контекст первого метода, кладём в него вернувшееся из второго метода значение, если оно есть, и продолжаем исполнять первый метод.
\textbf{Сигнатура метода} - это имя метода и его параметры. В сигнатуру метода не входит возвращаемое значение. Нельзя написать два метода с одинаковой сигнатурой.
\textbf{Перегрузка методов} - это механизм языка, позволяющий написать методы с одинаковыми названиями и разными оставшимися частями сигнатуры, чтобы получить единообразие при вызове семантически схожих методов с разнотипными данными.
\item Написать метод «Шифр Цезаря», с булевым параметром зашифрования и расшифрования и числовым ключом;
\item Написать метод, принимающий на вход массив чисел и параметр n. Метод должен осуществить циклический (последний элемент при сдвиге становится первым) сдвиг всех элементов массива на n позиций;
\item Написать метод, которому можно передать в качестве аргумента массив, состоящий строго из единиц и нулей (целые числа типа \code{int}). Метод должен заменить единицы в массиве на нули, а нули на единицы и не содержать ветвлений. Написать как можно больше вариантов метода.