bannerbannerbanner
Объектно-ориентированное программирование на Java. Платформа Java SE

Тимур Машнин
Объектно-ориентированное программирование на Java. Платформа Java SE

Полная версия

Комментарии. Javadoc


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

Программы могут содержать сотни тысяч строк кода.

И очень сложно отслеживать все возможности.

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

Для этого можно использовать комментарии.

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



Еще одна возможность – это изготовить сопроводительную документацию к программе.

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



Если вы используете эти специальные комментарии, вы можете автоматически создать хорошую документацию.

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

Здесь мы видим один из возможных способов написания комментария.



Комментарий начинается с косой черты и звездочки и заканчивается звездочкой и косой чертой.

Комментарий может включать в себя несколько строк.

Здесь у нас есть еще один комментарий.



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

Но на разных строках есть еще несколько звездочек.

И это указание для специальной программы под названием Javadoc.

Javadoc принимает в качестве входа Java-код с этими специальными комментариями и выдает документацию для ее использования программистами.

Специальные команды, такие как @param и @return, имеют смысл, который Javadoc понимает при подготовке итоговой документации.

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

Например, смартфон с операционной системой Android имеет более 12 миллионов строк кода.

Из них более 2 миллионов написано на языке Java.

Представьте себе, что вы кодируете все эти строки самостоятельно.

Вам понадобится много времени.

Как программистам, нам нужно работать с другими программистами для достижения цели.

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

Также и другие программисты вполне вероятно будут работать с нашим кодом.

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

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

Эти примечания в программе – это то, что мы называем комментариями.

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

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

Как и во всем, что есть в жизни, существуют разные подходы в том, как мы можем писать эти комментарии.

Комментарии полезны для разных целей.

Например, описание кода, то есть резюмирование целей сегмента кода.

Описание алгоритма, который вы создаете.

Комментирование сегмента кода, который не работает должным образом.

Или автоматическое создание документации.

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

Сначала, мы сосредоточимся на тех типах комментариев, которые направленны на предоставление сведений о вашем коде вам и другим программистам.

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



И комментарий будет идти до конца строки.

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

И мы закончим комментарий звездочкой, а затем косой чертой.

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

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

Существуют рекомендации по написанию кода на языке Java.

Советуют использовать комментарии с несколькими строками только при комментировании блока кода.

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

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

Для этого нет однозначного ответа.

Убедитесь, что ваши комментарии соответствуют вашему коду.

Не забывайте обновлять свои комментарии при изменении кода.

Хороший программист создает не только хороший код, но также предоставляет другим возможность использовать свой код.

То есть, дает хорошие комментарии.

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

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

Документация в Java-коде должна начинаться с косой черты, а затем идут две звездочки, и заканчивается одной звездочкой, а затем косой чертой.



Javadoc просматривает вашу программу, ища строки, начинающиеся с косой черты и двух звездочек, и создает HTML-документацию.

Но почему мы должны использовать этот комментарий?

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

Когда мы говорим в Java об автоматической генерации документации, мы используем термин Javadoc.

Какую информацию мы должны включить в Javadoc?

На сайте Oracle вы можете найти руководство по эффективной практике написания комментариев для инструмента Javadoc.

Мы попытаемся обобщить наиболее важные из них, используя пример.

Мы начнем с определения Javadoc-комментария.

Комментарий Javadoc написан в формате HTML и должен предшествовать коду.

Он состоит из двух частей: описания и блока тегов.

Рассмотрим теги, которые вы должны использовать и как их использовать.

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

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

Если в вашем Javadoc-комментарии есть несколько абзацев, разделите их тэгом p.

Затем вставьте пустую строку комментария, между описанием и блоком тегов.

Обратите внимание, что каждый комментарий Javadoc имеет только одно описание.

И как только инструмент Javadoc найдет пустую строку, он решит, что описание закончено.

Затем вы используете теги для добавления информации о вашем методе.

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

Какая информация должна быть включена в блок тегов?

Для описания метода нам понадобятся, в основном, два типа тегов – @param и @return.

@param описывает аргумент метода.

И его необходимо указать для всех аргументов метода.

За тегом всегда следует имя аргумента.

Это имя всегда указывается в нижнем регистре.

Затем идет описание аргумента.

Далее вы должны всегда указывать тип данных аргумента.

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

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

Теги @param должны быть перечислены в порядке объявления аргумента.

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

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

Таким образом, Javadoc – это полезный инструмент, который позволяет программистам автоматически генерировать HTML-страницы с документацией из их кода.

Исключения


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

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



Это сделает нашу программу более надежной.

Таким образом, у нас есть исключения.

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

 

Мы программируем, что делать для значений, которые не желательны.

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

И они прекрасно компилируются.

Но когда мы их запускаем, возникают ошибки.

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

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

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

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

Кроме того, исключения позволяют классифицировать и дифференцировать типы ошибок систематическим образом.

Таким образом, исключение – это ошибка, возникающая во время выполнения программы. Исключения могут возникать во многих случаях, например:

Пользователь ввел некорректные данные.

Или файл, к которому обращается программа, не найден.

Или сетевое соединение с сервером было утеряно во время передачи данных.

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

Первое исключение, которое мы здесь видим, это ArithmeticException.



Это исключение выбрасывается при возникновении арифметических ошибок, например, при делении целого на ноль.

Эти сегменты кода вызывают исключение ArithmeticException.

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

Другим распространенным исключением является ArrayIndexOutOfBoundsException.



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

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

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



Еще одно исключение, это NumberFormatException.



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

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



Здесь вы видите, что код метода printDivision заключен в выражение try-catch.

В этом случае нет необходимости проверять значение b, делителя.

Если b отличен от нуля, будет выполнен метод System.out.printIn (a / b);

В противном случае Java выбросит исключение ArithmeticException с сообщением, что вы не можете делить на ноль.

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

Выражение try-catch также может быть применено к примерам ArrayIndexOutOfBoundsException и NumberFormatException, которые мы видели ранее.



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

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



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

И уже в вызывающем коде обработать вызов этого метода блоком try-catch.

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



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

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

Блок try может иметь несколько блоков catch, каждый из которых имеет дело с конкретным исключением.



Если блок try генерирует исключение, то соответствующий блок catch обработает исключение, и программа будет продолжена.

Встроенные исключения Java имеют определенную иерархию.



Все классы, представляющие ошибки являются наследниками класса java.lang.Throwable.

Только объекты этого класса или его наследников могут быть «выброшены» JVM при возникновении какой-нибудь исключительной ситуации, а также только эти исключения могут быть «выброшены» во время выполнения программы с помощью ключевого слова throw.

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

Также нужно учитывать, что все исключения делятся на «проверяемые» (checked) и «непроверяемые» (unchecked).

checked exception – проверяемое исключение, которое проверяется компилятором.

Throwable и Exception и все их наследники, за исключением наследников Error-а и RuntimeException – проверяемые.

Error и RuntimeException и все их наследники – не проверяемые компилятором исключения.

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

И так как проверяемое исключение проверяется во время компиляции, возникнет ошибка компиляции, если проверяемое исключение не обработано блоком try-catch, или оно не объявлено в заголовке или сигнатуре метода с помощью ключевого слова throws.



Так почему не все исключения являются проверяемыми?

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

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

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

Эту проверку создатели языка оставили программисту на его усмотрение.



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

Это исключение IndexOutOfBoundsException – выбрасывается, когда индекс некоторого элемента в структуре данных не попадает в диапазон имеющихся индексов.

Исключение NullPointerException – выбрасывается, когда ссылка на объект, к которому вы обращаетесь, хранит null.

Исключение ClassCastException – это ошибка приведения типов.

И исключение ArithmeticException – выбрасывается, когда выполняются недопустимые арифметические операции, например, деление на ноль.

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

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

Исключение StackOverflowError – ошибка переполнение стека. Часто возникает в рекурсивных функциях из-за неправильного условия выхода.

И исключение OutOfMemoryError – ошибка переполнения памяти.

Из описания этих не проверяемых исключений видно, что обработать все эти возможные ситуации в коде невозможно, иначе весь код – это будет сплошной try-catch.

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

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

Иными словами, Exception не должен находиться выше ArithmeticException и ArrayIndexOutOfBoundsException.

И еще, операторы try могут быть вложенными.

Если вложенный оператор try не имеет своего обработчика catch для определения исключения, то идёт поиск обработчика catch у внешнего блока try и т. д.

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

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



Теперь, есть еще одна конструкция в обработке исключений, это блок finally.

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

Это может стать источником проблем.

Например, при входе метод открывает файл и закрывает при выходе.

Чтобы закрытие файла не было пропущено из-за обработки исключения, используется блок finally.

Ключевое слово finally создаёт блок кода, который будет выполнен после завершения блока try/catch, но перед кодом, следующим за ним.

Блок будет выполнен, независимо от того, передано исключение или нет.

Оператор finally не обязателен, однако каждый оператор try требует наличия либо catch, либо finally.

Таким образом, блок finally всегда выполняется, когда блок try завершается.

Это гарантирует, что блок finally будет выполнен, даже если произойдет непредвиденное исключение.

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

Включение необходимого для выполнения кода в блок finally всегда является хорошей практикой, даже если не ожидается никаких исключений.

Однако блок finally не всегда может выполняться.

Если виртуальная машина JVM завершает работу во время выполнения кода try или catch, блок finally может не выполняться.

Аналогично, если поток, выполняющий код try или catch, прерывается или убивается, блок finally может не выполняться, даже если программа в целом продолжается.

Блок finally – это ключевой инструмент для предотвращения утечек ресурсов.

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

Рассмотрим этот пример.



Каким здесь может быть вывод в консоль?

Здесь вполне возможна ситуация, когда в консоль сначала будет выведено сообщение об ошибке, а только потом вывод System.out.println.

Так как вывод System. out является буферизированным, то есть сообщения сначала помещаются в буфер, прежде они будут выведены в консоль.

А сообщение необработанного исключение выводится через не буферизированный вывод System.err.

Как уже было сказано, каждый оператор try требует наличия либо catch, либо finally.



Поэтому возможна конструкция try – finally.

 

И блок finally получит управление, даже если try-блок завершится исключением.

И блок finally получит управление, даже если try-блок завершится директивой выхода из метода.

Однако блок finally НЕ будет вызываться, если мы убъем виртуальную машину JVM.

При всем при этом, надо отметить, что блок finally не перехватывает исключение, и программа завершиться ошибкой при возникновении в блоке try исключения.

Исключение перехватывает только блок catch.

Таким образом мы разобрали почти все случаи работы операторов try, catch, throws, throw, и finally.

Рекурсия


В некоторых случаях нам нужно выполнять повторные вычисления.

И мы видели циклы for и while, которые выполняют повторные вычисления.

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

Ранее мы определили метод square, который, принимая целое число, возвращает квадрат числа.



Теперь мы хотели бы определить метод, который возводит в степень.

Мы хотим определить метод, который, учитывая базу x и показатель y, вычисляет x в степени y.



Поэтому, если y равно 2, мы вычисляем квадрат числа, как и раньше.

Вы видите, что в этом методе мы имеем два аргумента, целые числа x и y.

Давайте сначала попытаемся определить этот метод.

Давайте проанализируем несколько случаев.

Если y равно 0, то результат x равен степени 0, т. е. 1.

Если y равно 1, результат будет сразу x.

Если y равно 2, результатом является квадрат x.

Мы можем вызвать метод square, который мы определили ранее.

Если y равно 3, мы имеем x в кубе, предполагая, что у нас есть метод, называемый cube, определенный заранее.

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



Теперь мы можем заменить вызовы методов square, cube, и т. д. следующим кодом.

Таким образом, мы будем иметь x умножить на x, x умножить на x умножить на x и т. д.



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

Но мы все же кое-чему научились.

Чтобы вычислить x в степени y, мы должны умножить x y раз.

Но мы должны учитывать, является ли эта процедура применима для всех целых чисел y?

Нет.

Только для y больше или равно 0.

Для отрицательного y нам понадобится другой способ умножения.

Если у нас есть повторное умножение, мы можем использовать цикл.



Вот пример того, как мы можем это сделать.

Мы инициализируем целочисленную переменную z в 1, а затем вводим цикл.

Счетчик i инициализируется 1 и увеличивается на 1 при каждом прогоне цикла.

Этот счетчик отслеживает, сколько х мы умножаем и накапливаем с помощью z.

И мы должны выполнять тело цикла ровно y раз, пока i не станет равен y.

Затем мы выходим и возвращаем накопленное значение в z.

Давайте проанализируем это снова.



x в степени y равно 1, если y равно 0.

А если y строго больше 0, то x в степени y равно x умножить на x в степени y минус 1.

Это то, что в математике называется рекуррентным уравнением.

И мы можем написать это на Java в виде вызова функции power.

Если y равно 0, возвращаем 1.



Иначе, возвращаем x умножить на вызов этой же функции с x и y минус 1.

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

Оба эти способа эквивалентны.

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

Рекурсию можно сравнить с матрешкой.



Чтобы понять это вернемся к рекурсивному методу, который мы определили.

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

Начнем с x в 3 степени.

Мы можем заменить вызов метода, используя определение метода.



Таким образом, мы пишем весь код метода, подставляя вместо y 3.

И в этой последовательности выражений мы переходим от вызова метода с параметрами (x, 3) к вызову метода с параметрами (x, 2).

Пишем весь код метода, подставляя вместо y 2.



И в этой последовательности выражений, мы перешли от вызова метода с параметрами (x, 2) к вызову метода с параметрами (x, 1).

И переходим к вызову метода с параметрами (x, 0).



x в степени 0 равно 1.



Теперь нам нужно собрать все вместе.

power (x, 3) равно x умножить на power (x, 2).



А power (x, 2) равно x умножить на power (x, 1).

А power (x, 1) равна x умножить на power (x, 0), что равно 1.

Таким образом, мы получаем x умножить на x умножить на x умножить на 1.

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

Это изображение коробки с медсестрой, держащей меньшую коробку с тем же изображением.



Так что в теории, могут быть бесконечные медсестры и бесконечные коробки.

Но на практике нет бесконечных коробок, потому что изображение имеет некоторое разрешение, и мы не можем опуститься ниже 1 пикселя.

Таким образом, существует конечное число коробок.

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

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

Давайте рассмотрим, опять наш рекурсивный метод вычисления степени числа.

И давайте вызовем power (x, -2) для некоторого заданного x.



Для этого мы можем заменить вызов метода кодом.



В результате мы перейдем к вызову метода power (x, -3).

В методе power (x, -3) мы перейдем к вызову метода power (x, -4).



И так далее. Без конца.



Мы получим бесконечные вычисления в теории.

На практике мы получим переполнение в какой-то момент и ошибку.

Что же мы сделали не так?

В этом случае мы не соблюдали комментарий, что y должно быть больше или равно 0.

Поэтому мы должны учитывать две важные вещи.

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

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

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

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



Существует два способа чтения и понимания рекурсивных методов.

Один из них – это тот способ, который мы видели.

Другой, математический или нотационный способ, которые мы рассмотрим.

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

Начнем с относительно простой задачи – написать метод на Java для вычисления факториала натурального числа.

В общем случае факториал натурального числа n вычисляется умножением всех натуральных чисел, начиная с 1 до n.



Чтобы решить эту задачу, мы будем использовать следующую стратегию.

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

Предположим, что нам нужно вычислить факториал натурального числа n, но мы уже знаем, как вычислить факториал n минус 1.



Если бы у нас был факториал n минус 1, мы просто бы умножили это число на n, чтобы получить факториал n.

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

Факториал 0 нельзя свести к более простому случаю, как мы это делали ранее.



Так что это базовый случай.

Мы просто говорим, что факториал 0 равен 1.

Таким образом, факториал n равен 1, если n равно 0, и факториал n равен n умножить на факториал n минус 1, если n больше 0.

Теперь у нас есть основа для записи рекурсивного метода.

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



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

Базовый случай получается из пограничного случая.

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

1  2  3  4  5  6  7  8  9  10  11  12  13  14  15  16  17  18  19  20  21  22  23  24  25  26  27 
Рейтинг@Mail.ru