Что для программы является исключительной ситуацией java
Обработка исключений в Java - непростая тема. Новичкам сложно понять, и даже опытные разработчики могут часами обсуждать, как и какие исключения следует создавать или обрабатывать.
Вот почему у большинства команд разработчиков есть собственный набор правил их использования. И если вы новичок в команде, вас может удивить, насколько эти правила могут отличаться от тех, которые вы использовали раньше.
Тем не менее, есть несколько передовых практик, которые используются большинством команд. Вот 9 самых важных из них, которые помогут вам начать работу или улучшить обработку исключений.
1. Освободите ресурсы в блоке finally или используйте инструкцию "Try-With-Resource"
Довольно часто вы используете ресурс в своем блоке try, например InputStream, который вам нужно закрыть позже. Распространенной ошибкой в таких ситуациях является закрытие ресурса в конце блока try.
Проблема в том, что этот подход работает отлично до тех пор, пока не генерируется исключение. Все операторы в блоке try будут выполнены, и ресурс будет закрыт.
Но вы не зря добавили блок try. Вы вызываете один или несколько методов, которые могут вызвать исключение, или, может быть, вы сами вызываете исключение. Это означает, что вы можете не дойти до конца блока try. И как следствие, вы не закроете ресурсы.
Поэтому вам следует поместить весь код очистки в блок finally или использовать оператор try-with-resource.
Используйте блок Finally
В отличие от последних нескольких строк вашего блока try, блок finally всегда выполняется. Это происходит либо после успешного выполнения блока try, либо после обработки исключения в блоке catch. Благодаря этому вы можете быть уверены, что освободите все захваченные ресурсы.
Оператор Java 7 "Try-With-Resource"
Другой вариант - это оператор try-with-resource, который я объяснил более подробно во введении в обработку исключений Java.
Вы можете использовать его, если ваш ресурс реализует интерфейс AutoCloseable. Это то, что делает большинство стандартных ресурсов Java. Когда вы открываете ресурс в предложении try, он автоматически закрывается после выполнения блока try или обработки исключения.
2. Конкретные исключения предпочтительнее
Чем конкретнее исключение, которое вы генерируете, тем лучше. Всегда помните, что коллеге, который не знает вашего кода, а может быть, и вам через несколько месяцев, необходимо вызвать ваш метод и обработать исключение.
Поэтому постарайтесь предоставить им как можно больше информации. Это упрощает понимание вашего API. В результате вызывающий ваш метод сможет лучше обработать исключение или избежать его с помощью дополнительной проверки.
Поэтому всегда старайтесь найти класс, который лучше всего подходит для вашего исключительного события, например, генерируйте NumberFormatException вместо IllegalArgumentException. И избегайте создания неспецифического исключения.
3. Документируйте определенные вами исключения
Каждый раз, когда вы определяете исключение в сигнатуре вашего метода, вы также должны задокументировать его в своем Javadoc. Это преследует ту же цель, что и предыдущая передовая практика: предоставить вызывающему как можно больше информации, чтобы он мог избежать или обработать исключение.
Итак, не забудьте добавить объявление @throws в свой Javadoc и описать ситуации, которые могут вызвать исключение.
Следовательно, он должен как можно точнее описать проблему и предоставить наиболее актуальную информацию для понимания исключительного события.
Не поймите меня неправильно; вы не должны писать абзац текста. Но вам следует объяснить причину исключения в 1-2 коротких предложениях. Это помогает вашей группе эксплуатации понять серьезность проблемы, а также упрощает анализ любых инцидентов, связанных с обслуживанием.
Если вы выберете конкретное исключение, его имя класса, скорее всего, уже будет описывать тип ошибки. Таким образом, вам не нужно предоставлять много дополнительной информации. Хорошим примером этого является NumberFormatException. Оно вызывается конструктором класса java.lang.Long, когда вы предоставляете String в неправильном формате.
5. Сначала перехватите наиболее конкретное исключение
Большинство IDE помогут вам в этой лучшей практике. Они сообщают о недостижимом блоке кода, когда вы сначала пытаетесь перехватить менее конкретное исключение.
Проблема в том, что выполняется только первый блок catch, соответствующий исключению. Итак, если вы сначала поймаете IllegalArgumentException, вы никогда не достигнете блока catch, который должен обрабатывать более конкретное NumberFormatException, потому что это подкласс IllegalArgumentException.
Всегда сначала перехватывайте наиболее конкретный класс исключения и добавляйте менее конкретные блоки перехвата в конец вашего списка.
Пример такого оператора try-catch представлен в следующем фрагменте кода. Первый блок catch обрабатывает все NumberFormatException, а второй - все IllegalArgumentException, которые не являются NumberFormatException.
6. Не перехватывайте Throwable
Throwable - это суперкласс всех исключений и ошибок. Вы можете использовать его в предложении catch, но никогда не должны этого делать!
Если вы используете Throwable в предложении catch, он не только перехватит все исключения; он также перехватит все ошибки. JVM выдает ошибки, чтобы указать на серьезные проблемы, которые не предназначены для обработки приложением. Типичными примерами этого являются OutOfMemoryError или StackOverflowError. И то, и другое вызвано ситуациями, которые находятся вне контроля приложения и не могут быть обработаны.
Итак, лучше не перехватывайте Throwable, если вы не абсолютно уверены, что находитесь в исключительной ситуации, в которой вы можете или обязаны обрабатывать ошибку.
7. Не игнорируйте исключения
Вы когда-нибудь анализировали отчет об ошибке, в котором выполнялась только первая часть вашего сценария использования?
Часто это вызвано игнорируемым исключением. Разработчик, вероятно, был уверен, что оно никогда не будет вызвано, и добавил блок catch, который не обрабатывает и не регистрирует его. И когда вы найдете этот блок, вы, скорее всего, даже найдете один из известных комментариев «Этого никогда не будет».
Что ж, возможно, вы анализируете проблему, в которой произошло невозможное.
Поэтому, пожалуйста, никогда не игнорируйте исключения. Вы не знаете, как код изменится в будущем. Кто-то может удалить проверку, которая предотвратила исключительное событие, не осознавая, что это создает проблему. Или код, который генерирует исключение, изменяется и теперь генерирует несколько исключений одного и того же класса, а вызывающий код не предотвращает их все.
8. Не пишите в лог сгенерированные исключения
Это, вероятно, наиболее часто игнорируемая передовая практика в списке. Вы можете найти множество фрагментов кода и даже библиотек, в которых исключение перехватывается, регистрируется и повторно генерируется.
Если вам нужно добавить дополнительную информацию, вы должны перехватить исключение и обернуть его в пользовательское. Но обязательно следуйте передовой практике номер 9.
Итак, перехватывайте исключение, только если вы хотите его обработать. В противном случае укажите это в сигнатуре метода и позвольте вызывающей стороне позаботиться об этом.
9. Оберните исключение, не обрабатывая его
Иногда лучше поймать стандартное исключение и превратить его в настраиваемое. Типичным примером такого исключения является бизнес-исключение для конкретного приложения или платформы. Это позволяет вам добавлять дополнительную информацию, а также вы можете реализовать специальную обработку для вашего класса исключения.
Резюме
Как вы видели, есть много разных вещей, которые вы должны учитывать, когда генерируете или перехватываете исключение. Большинство из них имеют цель улучшить читаемость вашего кода или удобство использования вашего API.
Чаще всего исключения являются одновременно механизмом обработки ошибок и средством связи. Поэтому вам следует обязательно обсудить передовые практики и правила, которые вы хотите применять, со своими коллегами, чтобы все понимали общие концепции и использовали их одинаково.
Привет! Не хочется тебе об этом говорить, но огромная часть работы программиста — это работа с ошибками. Причем чаще всего — со своими собственными. Так уж сложилось, что не бывает людей, которые не допускают ошибок. И программ таких тоже не бывает. Конечно, главное при работе над ошибкой — понять ее причину. А причин таких в программе может быть целая куча. В один прекрасный момент перед создателями Java встал вопрос: что делать с этими самыми потенциальными ошибками в программах? Избежать их полностью — нереально. Программисты могут понаписать такого, что невозможно даже представить :) Значит, надо заложить в язык механизм работы с ошибками. Иными словами, если уж в программе произошла какая-то ошибка, нужен сценарий для дальнейшей работы. Что именно программа должна делать при возникновении ошибки? Сегодня мы познакомимся с этим механизмом. И называется он “Исключения” ( Exceptions ).
Что такое исключение в Java
- FileNotFoundException (файл не найден)
- ArithmeticException (исключение при выполнении математической операции)
- ArrayIndexOutOfBoundsException (указан номер ячейки массива за пределами его длины). Например, если попытаться вывести в консоль ячейку array[23] для массива array длиной 10.
Перехват и обработка исключений
Для работы с исключениями в Java существуют специальные блоки кода: try , catch и finally . Код, в котором программист ожидает возникновения исключений, помещается в блок try . Это не значит, что исключение в этом месте обязательно произойдет. Это значит, что оно может там произойти, и программист в курсе этого. Тип ошибки, который ты ожидаешь получить, помещается в блок catch (“перехват”). Сюда же помещается весь код, который нужно выполнить, если исключение произойдет. Вот пример: Вывод: Мы поместили наш код в два блока. В первом блоке мы ожидаем, что может произойти ошибка “Файл не найден”. Это блок try . Во втором — указываем программе что делать, если произошла ошибка. Причем ошибка конкретного вида — FileNotFoundException . Если мы передадим в скобки блока catch другой класс исключения, оно не будет перехвачено. Вывод: Код в блоке catch не отработал, потому что мы “настроили” этот блок на перехват ArithmeticException , а код в блоке try выбросил другой тип — FileNotFoundException . Для FileNotFoundException мы не написали сценарий, поэтому программа вывела в консоль ту информацию, которая выводится по умолчанию для FileNotFoundException . Здесь тебе нужно обратить внимание на 3 вещи. Первое. Как только в какой-то строчке кода в блоке try возникнет исключение, код после нее уже не будет выполнен. Выполнение программы сразу “перепрыгнет” в блок catch . Например: Вывод: В блоке try во второй строчке мы попытались разделить число на 0, в результате чего возникло исключение ArithmeticException . После этого строки 6-10 блока try выполнены уже не будут. Как мы и говорили, программа сразу начала выполнять блок catch . Второе. Блоков catch может быть несколько. Если код в блоке try может выбросить не один, а несколько видов исключений, для каждого из них можно написать свой блок catch . В этом примере мы написали два блока catch . Если в блоке try произойдет FileNotFoundException , будет выполнен первый блок catch . Если произойдет ArithmeticException , выполнится второй. Блоков catch ты можешь написать хоть 50. Но, конечно, лучше не писать код, который может выбросить 50 разных видов ошибок :) Третье. Откуда тебе знать, какие исключения может выбросить твой код? Ну, про некоторые ты, конечно, можешь догадываться, но держать все в голове невозможно. Поэтому компилятор Java знает о самых распространенных исключениях и знает, в каких ситуациях они могут возникнуть. Например, если ты написал код и компилятор знает, что при его работе могут возникнуть 2 вида исключений, твой код не скомпилируется, пока ты их не обработаешь. Примеры этого мы увидим ниже. Теперь что касается обработки исключений. Существует 2 способа их обработки. С первым мы уже познакомились — метод может обработать исключение самостоятельно в блоке catch() . Есть и второй вариант — метод может выбросить исключение вверх по стеку вызовов. Что это значит? Например, у нас в классе есть метод — все тот же printFirstString() , который считывает файл и выводит в консоль его первую строку: На текущий момент наш код не компилируется, потому что в нем есть необработанные исключения. В строке 1 ты указываешь путь к файлу. Компилятор знает, что такой код легко может привести к FileNotFoundException . В строке 3 ты считываешь текст из файла. В этом процессе легко может возникнуть IOException — ошибка при вводе-выводе данных (Input-Output). Сейчас компилятор говорит тебе: “Чувак, я не одобрю этот код и не скомпилирую его, пока ты не скажешь мне, что я должен делать в случае, если произойдет одно из этих исключений. А они точно могут произойти, исходя из того кода, который ты написал!”. Деваться некуда, нужно обрабатывать оба! Первый вариант обработки нам уже знаком: надо поместить наш код в блок try , и добавить два блока catch : Но это не единственный вариант. Мы можем не писать сценарий для ошибки внутри метода, и просто пробросить исключение наверх. Это делается с помощью ключевого слова throws , которое пишется в объявлении метода: После слова throws мы через запятую перечисляем все виды исключений, которые этот метод может выбросить при работе. Зачем это делается? Теперь, если кто-то в программе захочет вызвать метод printFirstString() , он должен будет сам реализовать обработку исключений. К примеру, в другой части программы кто-то из твоих коллег написал метод, внутри которого вызывает твой метод printFirstString() : Ошибка, код не компилируется! В методе printFirstString() мы не написали сценарий обработки ошибок. Поэтому задача ложится на плечи тех, кто будет этот метод использовать. То есть перед методом yourColleagueMethod() теперь стоят те же 2 варианта: он должен или обработать оба исключения, которые ему “прилетели”, с помощью try-catch , или пробросить их дальше. Во втором случае обработка ляжет на плечи следующего по стэку метода — того, который будет вызывать yourColleagueMethod() . Вот поэтому такой механизм называется “пробрасыванием исключения наверх”, или “передачей наверх”. Когда ты пробрасываешь исключения наверх с помощью throws , код компилируется. Компилятор в этот момент как бы говорит: “Окей, ладно. Твой код содержит кучу потенциальных исключений, но я, так и быть, его скомпилирую. Мы еще вернемся к этому разговору!” И когда ты где-то в программе вызываешь метод, который не обработал свои исключения, компилятор выполняет свое обещание и снова напоминает о них. В завершении мы поговорим о блоке finally (простите за каламбур). Это последняя часть триумвирата обработки исключений try-catch-finally . Его особенность в том, что он выполняется при любом сценарии работы программы. В этом примере код внутри блока finally выполняется в обоих случаях. Если код в блоке try выполнится целиком и не выбросит исключения, в конце сработает блок finally . Если код внутри try прервется, и программа перепрыгнет в блок catch , после того, как отработает код внутри catch , все равно будет выбран блок finally . Зачем он нужен? Его главное назначение — выполнить обязательную часть кода; ту часть, которая должна быть выполнена независимо от обстоятельств. Например, в нем часто освобождают какие-то используемые программой ресурсы. В нашем коде мы открываем поток для чтения информации из файла и передаем его в объект BufferedReader . Наш reader нужно закрыть и освободить ресурсы. Это нужно сделать в любом случае: неважно, отработает программа как надо или вызовет исключение. Это удобно делать в блоке finally : Теперь мы точно уверены, что позаботились о занятых ресурсах независимо от того, что произойдет при работе программы :) Это еще не все, что тебе нужно знать об исключениях. Обработка ошибок — очень важная тема в программировании: ей посвящена не одна статья. На следующем занятии мы узнаем, какие бывают виды исключений и как создать свое собственное исключение:) До встречи!
Что такое исключения / exception java?
В мире программирования возникновение ошибок и непредвиденных ситуаций при выполнении программы называют исключением. В программе исключения могут возникать в результате неправильных действий пользователя, отсутствии необходимого ресурса на диске, или потери соединения с сервером по сети. Причинами исключений при выполнении программы также могут быть ошибки программирования или неправильное использование API. В отличие от нашего мира, программа должна четко знать, как поступать в такой ситуации. Для этого в Java предусмотрен механизм исключений.
Кратко о ключевых словах try, catch, finally, throws
- try – определяет блок кода, в котором может произойти исключение;
- catch – определяет блок кода, в котором происходит обработка исключения;
- finally – определяет блок кода, который является необязательным, но при его наличии выполняется в любом случае независимо от результатов выполнения блока try.
- throw – используется для возбуждения исключения;
- throws – используется в сигнатуре методов для предупреждения, о том что метод может выбросить исключение.
Для чего нужен механизм исключений?
Посмотрим на пример из реального мира. Представьте, что на автомобильной дороге есть участок с аварийным мостом с ограниченной грузоподъемностью. Если по нему поедет автомобиль с массой, превышающей грузоподъемность моста, он может разрушиться, и ситуация для водителя может стать, мягко говоря, исключительной. Чтобы этого не произошло, дорожная служба заблаговременно устанавливает предупредительные знаки на дороге. Водитель автомобиля, глядя на предупреждающий знак, будет сравнивать массу своего автомобиля с разрешенной для проезда по мосту. Если она превышает ее – он поедет по объездному пути. Благодаря действиям дорожной службы водители грузового транспорта, во-первых, получили возможность заблаговременно изменять свой путь, во-вторых, предупреждены об опасности на основном пути, и, наконец, предупреждены о невозможности использования моста при определенных условиях.
Возможность предупреждения и разрешения исключительной ситуации в программе для ее продолжения – одна из причин использования исключений в Java. Механизм исключений также позволяет защитить написанный вами код (программный интерфейс) от неправильного использования пользователем за счет валидации (проверки) входящих данных. Давайте теперь на секунду побудем дорожной службой. Во-первых, вы должны знать места, где автомобилистов могут ждать неприятности. Во-вторых, вам нужно заготовить и установить предупредительные знаки. И, наконец, вам нужно предусмотреть объездные маршруты в случае опасности на основном пути. В Java механизм исключений работает похожим образом. На стадии разработки программы мы «ограждаем» опасные участки кода в отношении исключений с помощью блока try<>, предусматриваем «запасные» пути с помощью блока catch<>, в блоке finally<> мы пишем код, который выполняется в программе при любом исходе. В случаях, когда мы не можем предусмотреть «запасной путь» или намеренно хотим предоставить право его выбора пользователю, мы должны, по крайней мере, предупредить его об опасности. Почему? А вы только вообразите негодование водителя, который доедет до аварийного моста, по которому нельзя проехать, не встретив по дороге ни одного предупреждающего знака! В программировании при написании своих классов и методов мы не всегда можем предвидеть контекст их использования другими разработчиками в своих программах, поэтому не можем предвидеть на 100% правильный путь для разрешения исключительной ситуации. В то же время, правило хорошего тона — предупредить пользователей нашего кода о возможности исключительной ситуации. Механизм исключений Java позволяет нам сделать это с помощью throws – по сути, объявления общего поведения нашего метода, заключающееся в выбрасывании исключения, и предоставляя, таким образом, написание кода по обработке исключения в Java пользователю метода.
Предупреждение о «неприятностях»
Когда вы не планируете обрабатывать исключение в своем методе, но хотите предупредить пользователей метода о возможных исключительных ситуациях — используйте ключевое слово throws. Это ключевое слово в сигнатуре метода означает, что при определенных условиях метод, может выбросить исключение. Такое предупреждение является частью интерфейса метода и предоставляет право пользователю на собственный вариант реализации обработчика исключения. После throws мы указываем тип выбрасываемого исключения. Обычно это наследники класса Exception Java. Поскольку Java является объектно-ориентированным языком, все исключения в Java представляют собой объекты.
Иерархия исключений Java
- Ситуации, при которых восстановление дальнейшей нормальной работы программы невозможно
- Восстановление возможно.
Создание исключения
При исполнении программы исключение генерируется JVM или вручную, с помощью оператора throw . При этом в памяти создается объект исключения и выполнение основного кода программы прерывается, а обработчик исключений JVM пытается найти способ обработать исключение.
Обработка исключения
Создание блоков кода, для которых мы предусматриваем обработку исключений в Java, производится в программе с помощью конструкций try<>catch, try<>catch<>finally, try<>finally<>.
При возбуждении исключения в блоке try обработчик исключения ищется в следующем за ним блоке catch. Если в catch есть обработчик данного типа исключения – управление переходит к нему. Если нет, то JVM ищет обработчик этого типа исключения в цепочке вызовов методов до тех пор, пока не будет найден подходящий catch. После выполнения блока catch управление передается в необязательный блок finally . В случае, если подходящий блок catch не найден, JVM останавливает выполнение программы, и выводит стек вызовов методов – stack trace , выполнив перед этим код блока finally при его наличии. Пример обработки исключений: Результаты работы метода main : Блок finally обычно используется для того, чтобы закрыть открытые в блоке try потоки или освободить ресурсы. Однако при написании программы не всегда возможно уследить за закрытием всех ресурсов. Для облегчения нашей жизни разработчики Java предложили нам конструкцию try-with-resources , которая автоматически закрывает ресурсы, открытые в блоке try. Наш первый пример можно переписать так с помощью try-with-resources : Благодаря возможностям Java, начиная с версии 7, мы также можем объединять перехват разнотипных исключений в одном блоке, делая код более компактным и читабельным. Например:
Наконец-то программисты додумались регламентировать и автоматизировать обработку ошибок. Произошло это когда изобрели исключения . Сейчас с помощью механизма исключений в мире обрабатывается 80% внештатных ситуаций.
Если исключения придумал какой-нибудь ученый, то скорее всего он защитил на этом докторскую диссертацию. Если же придумал программист, то он, возможно, получил дружеское похлопывание по плечу от коллег: «Вроде норм, бро».
Когда в Java-программе возникает ошибка, например, деление на 0 , происходят такие замечательные вещи:
Создается специальный объект-исключение, в котором содержится информация о произошедшей ошибке.
Все в Java является объектом, и исключения — не исключение 🙂 Объекты-исключения имеют свои классы, и все их отличие от обычных классов в том, что они унаследованы от класса Throwable .
Объект-исключение «выбрасывается». Не сильно удачное название. «Выбрасывание исключения» по своей сути больше похоже на включение пожарной сигнализации или оповещение «боевая тревога».
Когда в систему «выброшено исключение», нормальный режим работы программы прекращается, и начинается «работа по аварийному протоколу».
Текущий метод, в котором возникло (выброшено) исключение, немедленно завершается. Исключение передается в вызывающий метод, который тоже немедленно завершается. И так по цепочке, пока не завершится метод main . Вместе с завершением метода main завершается и программа.
Код | Вывод на экран |
---|
В 20-й строке возникла ошибка — деление на 0 . Java-машина тут же создала исключение — объект класса ArithmeticException и «выбросила» его в систему.
Метод поделим() сразу завершился, поэтому на экран не вывелась строка Ничего страшного не произошло: 0. Программа вернулась в метод конецСвета() , и ситуация повторилась: в системе есть исключение, а значит, метод конецСвета() тоже аварийно завершается. Затем завершается метод main , и выполнение программы прекращается.
А в чем же смысл работы таких исключений? А в том, что в вашей программе можно перехватывать исключения определенного типа и писать свою логику обработки аварийных ситуаций.
2. Перехват исключений try-catch
В Java есть механизм перехвата исключений, который позволяет прекратить аварийное завершение методов. Выглядит он так:
Эта конструкция называется блок try-catch .
Код, в котором могут возникнуть исключения , оборачивается в фигурные скобки, перед которыми пишется слово try (пытаться).
После фигурных скобок пишется ключевое слово catch , внутри круглых скобок объявляется переменная типа-исключения . Затем следуют фигурные скобки, внутри которых пишется код, который нужно выполнить, если возникло исключение указанного типа.
Если во время выполнения « основного кода » исключений не возникло, код внутри блока catch выполняться не будет. Если же исключение возникло, будет (при условии, что тип возникшего исключения совпадает с типом переменной в круглых скобках).
Это первая часть статьи, посвященной такому языковому механизму Java как исключения (вторая (checked/unchecked) вот). Она имеет вводный характер и рассчитана на начинающих разработчиков или тех, кто только приступает к изучению языка.
1. Ключевые слова: try, catch, finally, throw, throws
- try
- catch
- finally
- throw
- throws
catch:
Годится
throw:
Годится
Кроме того, throw требуется не-null аргумент, иначе NullPointerException в момент выполнения
throw и new — это две независимых операции. В следующем коде мы независимо создаем объект исключения и «бросаем» его
Однако, попробуйте проанализировать вот это
2. Почему используем System.err, а не System.out
System.out — buffered-поток вывода, а System.err — нет. Таким образом вывод может быть как таким
Так и вот таким (err обогнало out при выводе в консоль)
Давайте это нарисуем
3. Компилятор требует вернуть результат (или требует молчать)
Если в объявлении метода сказано, что он возвращает НЕ void, то компилятор зорко следит, что бы мы вернули экземпляр требуемого типа или экземпляр типа, который можно неявно привести к требуемому
вот так не пройдет (другой тип)
Вот так не выйдет — нет возврата
и вот так не пройдет (компилятор не может удостовериться, что возврат будет)
Компилятор отслеживает, что бы мы что-то вернули, так как иначе непонятно, что должна была бы напечатать данная программа
Из-забавного, можно ничего не возвращать, а «повесить метод»
Тут в d никогда ничего не будет присвоено, так как метод sqr повисает
Компилятор пропустит «вилку» (таки берем в квадрат ИЛИ висим)
Но механизм исключений позволяет НИЧЕГО НЕ ВОЗВРАЩАТЬ!
Итак, у нас есть ТРИ варианта для компилятора
Но КАКОЙ ЖЕ double вернет функция, бросающая RuntimeException?
А НИКАКОЙ!
Подытожим: бросаемое исключение — это дополнительный возвращаемый тип. Если ваш метод объявил, что возвращает double, но у вас нет double — можете бросить исключение. Если ваш метод объявил, что ничего не возвращает (void), но у вам таки есть что сказать — можете бросить исключение.
Давайте рассмотрим некоторый пример из практики.
Задача: реализовать функцию, вычисляющую площадь прямоугольника
важно, что задание звучит именно так, в терминах предметной области — «вычислить площадь прямоугольника», а не в терминах решения «перемножить два числа»:
Вопрос: что делать, если мы обнаружили, что хотя бы один из аргументов — отрицательное число?
Если просто умножить, то мы пропустили ошибочные данные дальше. Что еще хуже, возможно, мы «исправили ситуацию» — сказали что площадь прямоугольника с двумя отрицательными сторонами -10 и -20 = 200.
Мы не можем ничего не вернуть
Можно, конечно, отписаться в консоль, но кто ее будет читать и как определить где была поломка. При чем, вычисление то продолжится с неправильными данными
Можно вернуть специальное значение, показывающее, что что-то не так (error code), но кто гарантирует, что его прочитают, а не просто воспользуются им?
Можем, конечно, целиком остановить виртуальную машину
и другие операторы.
- вызов метода: создаем новый фрейм, помещаем его на верхушку стека и переходим в него
- выход из метода: возвращаемся к предыдущему фрейму (через return или просто кончились инструкции в методе)
throw — выходим из ВСЕХ фреймов
Останавливаем через 1 фрейм (фактически аналог return, просто покинули фрейм «другим образом»)
Итак, давайте сведем все на одну картинку
5. try + catch (catch — полиморфен)
Напомним иерархию исключений
То, что исключения являются объектами важно для нас в двух моментах
1. Они образуют иерархию с корнем java.lang.Throwable (java.lang.Object — предок java.lang.Throwable, но Object — уже не исключение)
2. Они могут иметь поля и методы (в этой статье это не будем использовать)
По первому пункту: catch — полиморфная конструкция, т.е. catch по типу Parent перехватывает летящие экземпляры любого типа, который является Parent-ом (т.е. экземпляры непосредственно Parent-а или любого потомка Parent-а)
Даже так: в блоке catch мы будем иметь ссылку типа Exception на объект типа RuntimeException
catch по потомку не может поймать предка
catch по одному «брату» не может поймать другого «брата» (Error и Exception не находятся в отношении предок-потомок, они из параллельных веток наследования от Throwable)
По предыдущим примерам — надеюсь вы обратили внимание, что если исключение перехвачено, то JVM выполняет операторы идущие ПОСЛЕ последних скобок try+catch.
Но если не перехвачено, то мы
1. не заходим в блок catch
2. покидаем фрейм метода с летящим исключением
А что будет, если мы зашли в catch, и потом бросили исключение ИЗ catch?
В таком случае выполнение метода тоже прерывается (не печатаем «3»). Новое исключение не имеет никакого отношения к try-catch
Мы можем даже кинуть тот объект, что у нас есть «на руках»
И мы не попадем в другие секции catch, если они есть
Обратите внимание, мы не напечатали «3», хотя у нас летит Error а «ниже» расположен catch по Error. Но важный момент в том, что catch имеет отношение исключительно к try-секции, но не к другим catch-секциям.
Как покажем ниже — можно строить вложенные конструкции, но вот пример, «исправляющий» эту ситуацию
6. try + catch + catch + .
Как вы видели, мы можем расположить несколько catch после одного try.
Но есть такое правило — нельзя ставить потомка после предка! (RuntimeException после Exception)
Ставить брата после брата — можно (RuntimeException после Error)
Как происходит выбор «правильного» catch? Да очень просто — JVM идет сверху-вниз до тех пор, пока не найдет такой catch что в нем указано ваше исключение или его предок — туда и заходит. Ниже — не идет.
Выбор catch осуществляется в runtime (а не в compile-time), значит учитывается не тип ССЫЛКИ (Throwable), а тип ССЫЛАЕМОГО (Exception)
7. try + finally
finally-секция получает управление, если try-блок завершился успешно
finally-секция получает управление, даже если try-блок завершился исключением
finally-секция получает управление, даже если try-блок завершился директивой выхода из метода
finally-секция НЕ вызывается только если мы «прибили» JVM
System.exit(42) и Runtime.getRuntime().exit(42) — это синонимы
И при Runtime.getRuntime().halt(42) — тоже не успевает зайти в finally
Однако finally-секция не может «починить» try-блок завершившийся исключение (заметьте, «more» — не выводится в консоль)
Трюк с «if (true) <. >» требуется, так как иначе компилятор обнаруживает недостижимый код (последняя строка) и отказывается его компилировать
И finally-секция не может «предотвратить» выход из метода, если try-блок вызвал return («more» — не выводится в консоль)
Однако finally-секция может «перебить» throw/return при помощи другого throw/return
finally-секция может быть использована для завершающего действия, которое гарантированно будет вызвано (даже если было брошено исключение или автор использовал return) по окончании работы
Например для освобождения захваченной блокировки
Или для закрытия открытого файлового потока
Специально для этих целей в Java 7 появилась конструкция try-with-resources, ее мы изучим позже.
Вообще говоря, в finally-секция нельзя стандартно узнать было ли исключение.
Конечно, можно постараться написать свой «велосипед»
Не рекомендуемые практики
— return из finally-секции (можем затереть исключение из try-блока)
— действия в finally-секции, которые могут бросить исключение (можем затереть исключение из try-блока)
8. try + catch + finally
Не заходим в catch, заходим в finally, продолжаем после оператора
Есть исключение и есть подходящий catch
Заходим в catch, заходим в finally, продолжаем после оператора
Есть исключение но нет подходящего catch
Не заходим в catch, заходим в finally, не продолжаем после оператора — вылетаем с неперехваченным исключением
9. Вложенные try + catch + finally
Операторы обычно допускают неограниченное вложение.
Пример с if
Суть в том, что try-cacth-finally тоже допускает неограниченное вложение.
Например вот так
Или даже вот так
Ну что же, давайте исследуем как это работает.
Вложенный try-catch-finally без исключения
Мы НЕ заходим в обе catch-секции (нет исключения), заходим в обе finally-секции и выполняем обе строки ПОСЛЕ finally.
Вложенный try-catch-finally с исключением, которое ПЕРЕХВАТИТ ВНУТРЕННИЙ catch
Мы заходим в ПЕРВУЮ catch-секцию (печатаем «3»), но НЕ заходим во ВТОРУЮ catch-секцию (НЕ печатаем «6», так как исключение УЖЕ перехвачено первым catch), заходим в обе finally-секции (печатаем «4» и «7»), в обоих случаях выполняем код после finally (печатаем «5»и «8», так как исключение остановлено еще первым catch).
Вложенный try-catch-finally с исключением, которое ПЕРЕХВАТИТ ВНЕШНИЙ catch
Мы НЕ заходим в ПЕРВУЮ catch-секцию (не печатаем «3»), но заходим в ВТОРУЮ catch-секцию (печатаем «6»), заходим в обе finally-секции (печатаем «4» и «7»), в ПЕРВОМ случае НЕ выполняем код ПОСЛЕ finally (не печатаем «5», так как исключение НЕ остановлено), во ВТОРОМ случае выполняем код после finally (печатаем «8», так как исключение остановлено).
Вложенный try-catch-finally с исключением, которое НИКТО НЕ ПЕРЕХВАТИТ
Мы НЕ заходим в ОБЕ catch-секции (не печатаем «3» и «6»), заходим в обе finally-секции (печатаем «4» и «7») и в обоих случаях НЕ выполняем код ПОСЛЕ finally (не печатаем «5» и «8», так как исключение НЕ остановлено), выполнение метода прерывается по исключению.
Контакты
Я занимаюсь онлайн обучением Java (вот курсы программирования) и публикую часть учебных материалов в рамках переработки курса Java Core. Видеозаписи лекций в аудитории Вы можете увидеть на youtube-канале, возможно, видео канала лучше систематизировано в этой статье.
Читайте также: