Последнее обновление: 30.10.2015
Нередко в процессе выполнения программы могут возникать ошибки, при том необязательно по вине разработчика. Некоторые из них трудно предусмотреть или предвидеть, а иногда и вовсе невозможно. Так, например, может неожиданно оборваться сетевое подключение при передаче файла. Подобные ситуации называются исключениями .
В языке Java предусмотрены специальные средства для обработки подобных ситуаций. Одним из таких средств является конструкция try...catch...finally . При возникновении исключения в блоке try управление переходит в блок catch, который может обработать данное исключение. Если такого блока не найдено, то пользователю отображается сообщение о необработанном исключении, а дальнейшее выполнение программы останавливается. И чтобы подобной остановки не произошло, и надо использовать блок try..catch. Например:
Int numbers = new int; numbers=45; System.out.println(numbers);
Так как у нас массив numbers может содержать только 3 элемента, то при выполнении инструкции numbers=45 консоль отобразит исключение, и выполнение программы будет завершено. Теперь попробуем обработать это исключение:
Try{ int numbers = new int; numbers=45; System.out.println(numbers); } catch(Exception ex){ ex.printStackTrace(); } System.out.println("Программа завершена");
При использовании блока try...catch вначале выполняются все инструкции между операторами try и catch. Если в блоке try вдруг возникает исключение, то обычный порядок выполнения останавливается и переходит к инструкции сatch. Поэтому когда выполнение программы дойдет до строки numbers=45; , программа остановится и перейдет к блоку catch
Выражение catch имеет следующий синтаксис: catch (тип_исключения имя_переменной) . В данном случае объявляется переменная ex , которая имеет тип Exception . Но если возникшее исключение не является исключением типа, указанного в инструкции сatch, то оно не обрабатывается, а программа просто зависает или выбрасывает сообщение об ошибке.
Но так как тип Exception является базовым классом для всех исключений, то выражение catch (Exception ex) будет обрабатывать практически все исключения. Обработка же исключения в данном случае сводится к выводу на консоль стека трассировки ошибки с помощью метода printStackTrace() , определенного в классе Exception.
После завершения выполнения блока catch программа продолжает свою работу, выполняя все остальные инструкции после блока catch.
Конструкция try..catch также может иметь блок finally . Однако этот блок необязательный, и его можно при обработке исключений опускать. Блок finally выполняется в любом случае, возникло ли исключение в блоке try или нет:
Try{ int numbers = new int; numbers=45; System.out.println(numbers); } catch(Exception ex){ ex.printStackTrace(); } finally{ System.out.println("Блок finally"); } System.out.println("Программа завершена");
В Java имеется множество различных типов исключений, и мы можем разграничить их обработку, включив дополнительные блоки catch:
Int numbers = new int; try{ numbers=45; numbers=Integer.parseInt("gfd"); } catch(ArrayIndexOutOfBoundsException ex){ System.out.println("Выход за пределы массива"); } catch(NumberFormatException ex){ System.out.println("Ошибка преобразования из строки в число"); }
Если у нас возникает исключение определенного типа, то оно переходит к соответствующему блоку catch.
Чтобы сообщить о выполнении исключительных ситуаций в программе, можно использовать оператор throw . То есть с помощью этого оператора мы сами можем создать исключение и вызвать его в процессе выполнения. Например, в нашей программе происходит ввод числа, и мы хотим, чтобы, если число больше 30, то возникало исключение:
Package firstapp; import java.util.Scanner; public class FirstApp { public static void main(String args) { try{ Scanner in = new Scanner(System.in); int x = in.nextInt(); if(x>=30){ throw new Exception("Число х должно быть меньше 30"); } } catch(Exception ex){ System.out.println(ex.getMessage()); } System.out.println("Программа завершена"); } }
Здесь для создания объекта исключения используется конструктор класса Exception, в который передается сообщение об исключении. И если число х окажется больше 29, то будет выброшено исключение и управление перейдет к блоку catch.
В блоке catch мы можем получить сообщение об исключении с помощью метода getMessage() .
Эта статья посвящается очень важному вопросу программирования - исключительным ситуациям и ошибкам (exceptions and errors).
В языке Java исключения (Exceptions) и ошибки (Errors) являются объектами. Когда метод вызывает, еще говорят "бросает" от слова "throws", исключительную ситуацию, он на самом деле работает с объектом. Но такое происходит не с любыми объектами, а только с теми, которые наследуются от Throwable.
Упрощенную диаграмму классов ошибок и исключительний вы можете увидеть на следующем рисунке:
RuntimeException, Error и их наследников еще называют unchecked exception , а всех остальных наследников класса Exception - checked exception .
Checked Exception обязывает пользователя обработать ее (использую конструкцию try-catch) или же отдать на откуп обрамляющим методам, в таком случае к декларации метода, который бросает проверяемое (checked) исключение, дописывают конструкцию throws , например:
Public Date parse(String source) throws ParseException { ... }
К unchecked исключениям относятся, например, NullPointerException, ArrayIndexOutOfBoundsException, ClassCastExcpetion и так далее. Это те ошибки, которые могут возникнут практически в любом методе. Несомненно, описывать каждый метод как тот, который бросает все эти исключения, было бы глупо.
1. Так когда же нужно бросать ошибки? . На этот вопрос можно ответить просто: если в методе возможна ситуация, которую метод не в состоянии обработать самостоятельно, он должен "бросать" ошибку. Но ни в коем случае нельзя использовать исключительные ситуации для управления ходом выполнения программы.
Чаще всего Exceptions бросаются при нарушении контракта метода. Контракт (contract) - это негласное соглашение между создателем метода (метод сделает и/или вернет именно то, что надо) и пользователем метода (на вход метода будут передаваться значения из множества допустимых).
Нарушение контракта со стороны создателя метода - это, например, что-нибудь на подобии MethodNotImplementedYetException:).
Пользователь метода может нарушить контракт, например, таким способом: на вход Integer.parseInt(String) подать строку с буквами и по заслугам получить NumberFormatException.
Часто при реализации веб-сервисов первыми строками методов я пишу конструкции вида:
Public Contract getContractById(String id) { if (id == null) throw new NullPointerException("id is null"); ... }
Это помогает на вызывающей стороне понять, что они нарушают контракт метода, причиной чего часто может быть ошибка в логике их же приложения.
2. А что собственно бросать? . Выбор не то чтобы сильно велик, но и не однозначен: checked, unchecked (runtime), unchecked (error).
Сразу скажу, в подавляющем большинстве случаев Error вам не понадобится. Это в основном критические ошибки (например, StackOverflowError), с которыми пусть работает JVM.
Checked Exceptions, как было написано выше, заставляет программиста-пользователя написать код для ее обработки или же описать метод как "вызывающий исключительную ситуацию".
С unchecked exception можно поступить по-разному. В случае с такими ошибками, пользователь сам решает, будет он обрабатывать эту ошибку, или же нет (компилятор не заставляет это делать).
Можно написать следующее простое правило: если некоторый набор входящих в метод данных может привести к нарушению контракта, и вы считаете, что программисту-пользователю важно разобраться с этим (и что он сможет это сделать), описывайте метод с конструкцией throws, иначе бросайте unchecked exception.
3. Ну и как это обрабатывать? Обрабатывать ошибку лучше там, где она возникла. Если в данном фрагменте кода нет возможности принять решение, что делать с исключением, его нужно бросать дальше, пока не найдется нужный обработчик, либо поток выполнения программы не вылетит совсем.
Вы наверняка знаете, что обработка исключений происходит с помощью блока try-catch-finally. Сразу скажу вам такую вещь: никогда не используйте пустой catch блок! . Выглядит этот ужас так:
Try { ... } catch(Exception e) { }
Если Вы уверены, что исключения в блоке try не возникнет никогда, напишите комментарий, как например в этом фрагменте кода:
StringReader reader = new StringReader("qwerty"); try { reader.read(); } catch (IOException e) { /* cannot happen */ }
Если же исключение в принципе может возникнуть, но только действительно в "исключительной ситуации" когда с ним ничего уже сделать будет нельзя, лучше оберните его в RuntimeException и пробросьте наверх, например:
String siteUrl = ...; ... URL url; try { url = new URL(siteUrl); } catch (MalformedURLException e) { throw new RuntimeException(e); }
Скорее всего ошибка здесь может возникнуть только при неправильной конфигурации приложения, например, siteUrl читается из конфигурационного файла и кто-то допустил опечатку при указании адреса сайта. Без исправления конфига и перезапуска приложения тут ничего поделать нельзя, так что RuntimeException вполне оправдан.
4. Зачем нужно все это делать? А почему бы и нет:). Если серьезно - правильное использование Exceptions и корректная их обработка сделают код более понятным, гибким, структурированным и возможным для повторного использования.
Updated 28.08.2009: Хочу показать вам несколько интересных моментов, которые касаются исключений и блоков try-catch-finally.
Можно ли сделать так, чтобы блок finally не выполнился? Можно:
Public class Test1 { public static void main(String args) { try { throw new RuntimeException(); } catch (Exception e) { System.exit(0); } finally { System.out.println("Please, let me print this."); } } }
Ну и еще одна интересная вещь. Какое исключение будет выброшено из метода:
Public class Test { public static void main(String args) { try { throw new NullPointerException(); } catch (NullPointerException e) { throw e; } finally { throw new IllegalStateException(); } } }
Правильный ответ - IllegalStateException. Не смотря на то, что в блоке catch происходит повторный "выброс" NPE, после него выполняется блок finally, который перехватывает ход выполнения программы и бросает исключение IllegalStateException.
Жду ваших вопросов и комментариев.
Комментариев: 2
Но ни в коем случае нельзя использовать исключительные ситуации для реализации частей бизнесс-логики.
Я бы сказал не стоит использовать исключения для управления flow алгоритма (типа выхода из вложенного цикла и все такое). А вот для реализации частей бизнес-логики как раз таки иногда и нужно, но только те которые checked. Классический пример - снятие денег со счета, когда выкидывается исключение, говорящее о том что денюшок то не хватает. Вот тут как раз клиенту метода снятия надо реализовать бизнес-логику по обработке такой ситуации.
Ну а в тех случаях когда речь идет о нарушении контракта метода (не бизнес-ограничений) не надо и задумываться - только unchecked, и ловить их должен код, находящийся в самом начале потока, и ничего кроме логирования информации об ошибки с ними сделать он не может, да и не должен.
P.S. Ну конечно бывают ситуации исключительные. Но это не более 20%.
lucker wrote: Ну а в тех случаях когда речь идет о нарушении контракта метода (не бизнес-ограничений) не надо и задумываться - только unchecked, и ловить их должен код, находящийся в самом начале потока, и ничего кроме логирования информации об ошибки с ними сделать он не может, да и не должен.
такой подход приведет к тому что код в начале потока будет в 80% случаев получать исключение неизвестно по какой причине и как возникшее, например стэк трэйс на вэб странице, ни один из модулей который будет использовать такой метод и понятия не будет иметь о том что гдето в глубине ктото выбросил анчекед исключение и результат его может быть самым удручающим, например: если такой метод кто-то начнет использовать в цикле то из-за одноко неправильного объекта переданого в метод, который выкинет анчекед из-за нарушения контракта, вместо скажем ожидаемого списка из нескольких строк на экране клиент не увидит ни одного, а мог бы просто увидеть на один меньше (тот который послужил причиной исключения) А произойдет это потому что девелопер который будет писать вывод понятия не будет иметь что данный конкретный метод может просто выкинуть тот или иной рантайм, если такой метод в случае нарушения контракта выбросит чекед исключение то пользователь метода будет иметь информацию и решит что ему делать с данным исключением, код будет иметь более предсказуемое поведение а следственно станет более професиональным.
Использование анчекед исключений где попало как раз и делает код мало пригодным к использованию, поскольку как раз скрывает тот самый контракт метода, когда пользователь метода понятия не имеет какие исключения могут вылететь из данного метода. А вылетать такие исключения как раз любят на продакшене во время демонстрации системы заказчику.
Статья кстати выглядит немного поверхностной, основная проблема в написании комерческого софта именно в том чтобы сделать его реюзабельным и легко изменяемым к постоянно меняющимся требованиям при этом надежным, предсказуемым и устойчивым. Поэтому приведенная цитата о том что 90% кода обрабатывает исключения а 10% делает работу - это цитата человека который врядли писал что-то что потом используют другие… поскольку работа програмы включает в себя и надежность и предсказуемость и повторяемость результатов и масштабируемость и возможность использовать код еще кем-то, все то что отличает комерческий код профи от кода студента.
Исключениями или исключительными ситуациями (состояниями) называются ошибки, возникшие в программе во время её работы.
Все исключения в Java являются объектами. Поэтому они могут порождаться не только автоматически при возникновении исключительной ситуации, но и создаваться самим разработчиком.
Иерархия классов исключений:
Исключения делятся на несколько классов, но все они имеют общего предка — класс Throwable. Его потомками являются подклассы Exception и Error.
Исключения (Exceptions) являются результатом проблем в программе, которые в принципе решаемы и предсказуемы. Например, произошло деление на ноль в целых числах.
Ошибки (Errors) представляют собой более серьёзные проблемы, которые, согласно спецификации Java, не следует пытаться обрабатывать в собственной программе, поскольку они связаны с проблемами уровня JVM. Например, исключения такого рода возникают, если закончилась память, доступная виртуальной машине. Программа дополнительную память всё равно не сможет обеспечить для JVM.
В Java все исключения делятся на три типа: контролируемые исключения (checked) и неконтролируемые исключения (unchecked), к которым относятся ошибки (Errors) и исключения времени выполнения (RuntimeExceptions, потомок класса Exception).
Контролируемые исключения представляют собой ошибки, которые можно и нужно обрабатывать в программе, к этому типу относятся все потомки класса Exception (но не RuntimeException).
Обработка исключения может быть произведена с помощью операторов try…catch, либо передана внешней части программы. Например, метод может передавать возникшие в нём исключения выше по иерархии вызовов, сам его не обрабатывая.
Неконтролируемые исключения не требуют обязательной обработки, однако, при желании, можно обрабатывать исключения класса RuntimeException.
Откомпилируем и запустим такую программу:
Class Main { public static void main(String args) { int a = 4; System.out.println(a/0); } }
В момент запуска на консоль будет выведено следующее сообщение:
Exception in thread "main" java.lang.ArithmeticException: / by zero at Main.main(Main.java:4)
Из сообщения виден класс случившегося исключения — ArithmeticException. Это исключение можно обработать:
Class Main { public static void main(String args) { int a = 4; try { System.out.println(a/0); } catch (ArithmeticException e) { System.out.println("Произошла недопустимая арифметическая операция"); } } }
Теперь вместо стандартного сообщения об ошибке будет выполняться блок catch, параметром которого является объект e соответствующего исключению класса (самому объекту можно давать любое имя, оно потребуется в том случае, если мы пожелаем снова принудительно выбросить это исключение, например, для того, чтобы оно было проверено каким-то ещё обработчиком).
В блок try при этом помещается тот фрагмент программы, где потенциально может возникнуть исключение.
Одному try может соответствовать сразу несколько блоков catch с разными классами исключений.
Import java.util.Scanner; class Main { public static void main(String args) { int m = {-1,0,1}; Scanner sc = new Scanner(System.in); try { int a = sc.nextInt(); m[a] = 4/a; System.out.println(m[a]); } catch (ArithmeticException e) { System.out.println("Произошла недопустимая арифметическая операция"); } catch (ArrayIndexOutOfBoundsException e) { System.out.println("Обращение по недопустимому индексу массива"); } } }
Если запустив представленную программу, пользователь введётся с клавиатуры 1 или 2, то программа отработает без создания каких-либо исключений.
Если пользователь введёт 0, то возникнет исключение класса ArithmeticException, и оно будет обработано первым блоком catch.
Если пользователь введёт 3, то возникнет исключение класса ArrayIndexOutOfBoundsException (выход за приделы массива), и оно будет обработано вторым блоком catch.
Если пользователь введёт нецелое число, например, 3.14, то возникнет исключение класса InputMismatchException (несоответствие типа вводимого значение), и оно будет выброшено в формате стандартной ошибки, поскольку его мы никак не обрабатывали.
Можно, однако, добавить обработчик для класса Exception, поскольку этот класс родительский для всех остальных контролируемых исключений, то он будет перехватывать любые из них (в том числе, и InputMismatchException).
Import java.util.Scanner; class Main { public static void main(String args) { int m = {-1,0,1}; int a = 1; Scanner sc = new Scanner(System.in); try { a = sc.nextInt(); m = 4/a; System.out.println(m[a]); } catch (ArithmeticException e) { System.out.println("Произошла недопустимая арифметическая операция"); } catch (ArrayIndexOutOfBoundsException e) { System.out.println("Обращение по недопустимому индексу массива"); } catch (Exception e) { System.out.println("Произошло ещё какое-то исключение"); } } }
Поскольку исключения построены на иерархии классов и подклассов, то сначала надо пытаться обработать более частные исключения и лишь затем более общие. То есть поставив первым (а не третьим) блок с обработкой исключения класса Exception, мы бы никогда не увидели никаких сообщений об ошибке, кроме «Произошло ещё какое-то исключение» (все исключения перехватились бы сразу этим блоком и не доходили бы до остальных).
Необязательным добавлением к блокам try…catch может быть блок finally. Помещенные в него команды будут выполняться в любом случае, вне зависимости от того, произошло ли исключение или нет. При том, что при возникновении необработанного исключения оставшаяся после генерации этого исключения часть программы — не выполняется. Например, если исключение возникло в процессе каких-то длительных вычислений, в блоке finally можно показать или сохранить промежуточные результаты.
2010, Алексей Николаевич Костин. Кафедра ТИДМ математического факультета МПГУ.
Любая программа будет работать стабильно только в том случае, если её исходный код отлажен, и в нем отсутствуют условия, которые могут вызывать непредвиденные ситуации. Процесс отлова возможных сбоев выполняется на стадии программирования. Для этого разработчик учитывает все предполагаемые исходы и пытается ограничить действие ошибки таким образом, чтобы она не смогла нарушить работу программы или привести к её краху.
В Java исключения могут быть вызваны в результате неправильного ввода данных пользователем, отсутствия необходимого для работы программы ресурса или внезапного отключения сети. Для комфортного использования созданного разработчиком приложения, необходимо контролировать появление внештатных ситуаций. Потребитель не должен ждать завершения работы зависшей программы, терять данные в результате необработанных исключений или просто часто появляющихся сообщений о том, что что-то пошло не так.
Что необходимо учитывать? Язык Java обладает своим встроенным функционалом обработки исключений. Конечно же большой процент ошибок отлавливается ещё на стадии компиляции, когда система автоматически сообщит о том, что использовать её дальше невозможно. Но существует и такой вид исключений, который возникает во время работы программы. Разработчик должен суметь предвидеть это и спроектировать код таким образом, чтобы он не вызвал ошибки, а обработал её особым способом или передал управление в другую ветку.
В Java такой отлов исключений навязывается компилятором, поэтому типичные проблемы известны и имеют свою стандартную схему исполнения.
Самым простым примером, при котором можно получить исключение — это деление. Несмотря на всю его простоту, в выражении, в качестве делителя, может оказаться ноль, что приведёт к ошибке. Хорошо, если его появление можно предсказать ранее и предотвратить. Но такой вариант доступен не всегда, поэтому отлов исключения нужно организовать непосредственно при возникновении «деления на ноль».
В Java механизм обработки перехвата ошибки выглядит так:
Простейший пример создания ошибки может выглядеть таким образом:
throw new NullPointerException();
Здесь переменная a проверяется на инициализацию, т.е. не равна ли ссылка на объект null. В случае, если такая ситуация возникла и нужна особая обработка, выбрасывается исключение с помощью throw new NullPointerException().
При работе с исключениями часто приходится использовать ключевые слова Java для обозначения того или иного действия. В данном языке программирования их пять:
Выброс в Java исключения, естественно предполагает, что оно будет особым образом обработано. Удобнее всего это сделать, если участок кода отгорожен в некий блок. Который возможно содержит исключение. При выполнении такого кода виртуальная машина найдёт непредвиденную ситуацию, поймёт, что находится в критическом блоке и передаст управление в участок с обработкой.
В Java код заворачивается в специальный блок try, внутри которого может быть сгенерировано исключение. Таким образом, в него помещается сразу несколько непредвиденных ситуаций, которые будут отловлены в одном месте, не расползаясь по коду.
Самый типичный код с блоком обработки выглядит так:
//Здесь будет определён код, который может породить исключение
} catch (Тип_исключения_1 идентификатор_1) {
} catch (Тип_исключения_2 идентификатор_2) {
//Здесь происходит обработка исключения, согласно его типу и условиям;
Ключевое слово catch сообщает о том, что код, подвергнутый проверке на исключение, нужно обработать так, как описано далее, при условии, что он соответствует его типу. Идентификатор может использоваться внутри блока кода обработки как аргументы.
Как стало понятно из предыдущей главы, блоки catch ловят исключения и обрабатывают их. Но очень часто возникает ситуация, когда должен выполниться некий код вне зависимости от того, были ли отловлены ошибки. Для этого существует ключевое слово finally. Оно применяется для увеличения значений различных счётчиков, закрытия файлов или соединений с сетью.
В данном участке представлены несколько блоков catch с придуманными методами отлова исключений. К примеру, код, содержащийся в try порождает непредвиденную ситуацию типа Cold. Тогда в консоль будут выведены выражения «Caught cold!» и «Is that something to cheer about?». То есть блок finally выполняется в любом случае.
На самом деле способ избежать запуска finally существует. Связан он с завершением работы виртуальной машины. Найти, как это реализовать, можно на просторах сети Интернет.
Throw генерирует исключение. Его синтаксис выглядит так:
throw new NewException();
Здесь создаётся новое исключение с типом NewException(). В качестве типа могут использоваться уже входящие в стандартные библиотеки Java классы и определённые ранее разработчиком собственного производства.
Такая конструкция входит в описание какого-либо метода, вызов которого затем должен происходить в рамках блока try, для того, чтобы была возможность его перехватить.
Что делать, если в процессе разработки возникла ситуация, когда метод может сгенерировать исключение, но не в состоянии правильно обработать. Для этого в сигнатуре метода указывается слово throws и тип возможного исключения.
Эта метка является своеобразным указателем для клиентских разработчиков о том, что метод не способен обработать своё же исключение. К тому же, если тип ошибки является проверяемым, то компилятор заставит явно это указать.
В Java версии 7 разработчики включили такое важное нововведение, как обработка блока try с ресурсами.
Многие создаваемые объекты в Java, после их использования должны быть закрыты для экономии ресурсов. Раньше приходилось это учитывать и останавливать такие экземпляры вручную. Теперь же в них появился интерфейс AutoClosable. Он помогает автоматически закрывать уже использованные объекты, помещённые в блок try. Благодаря такому подходу писать код стало удобней, в его читаемость значительно повысилась.
Создатели описываемого языка программирования учли многие аспекты при проектировании типов непредвиденных ситуаций. Однако, все варианты исхода событий предотвратить не получится, поэтому в Java реализована возможность определения своих собственных исключений, подходящих именно под нужды конкретного кода.
Простейший способ создания — унаследовать от наиболее подходящего к контексту объекта.
Здесь произошло наследование от Exception, класса, который используется для определения собственных исключений. В MyException имеются два конструктора — один по умолчанию, второй — с аргументом msg типа String.
Затем в public классе FullConstructors реализован метод f, сигнатура которого содержит throws MyException. Это ключевое слово означает, что f может выбросить исключение Java типа MyException. Далее в теле метода производится вывод текстовой информации в консоль и собственно сама генерация MyException, посредством throw.
Второй метод немного отличается от первого тем, что при создании исключения, ему передается строковый параметр, который будет отражён в консоли при отлове. В main видно, что f() и g() помещены в блок проверки try, а ключевое слово catch настроено на отлов MyException. Результатом обработки будет вывод сообщения об ошибке в консоль:
Таким образом получилось добавить исключения Java, созданные собственноручно.
Как и все объекты в Java, исключения также наследуются и имеют иерархическую структуру. Корневым элементом всех ошибок, выбрасываемых в этом языке программирования является класс java.lang.Throwable. От него наследуются два вида — Error и Exception.
Error — оповещает о критических ошибках и представляет собой непроверяемые исключения Java. Перехват и обработка таких данных в большинстве случаев происходит на стадии разработки и не нуждается во внедрении в код конечного приложения.
Наиболее часто используемым классом для создания и анализа исключений служит Exception. Который, в свою очередь, делится на несколько веток, в том числе RuntimeException. К RuntimeException относятся исключения времени выполнения, то есть происходящие во время работы программы. Все унаследованные от него классы являются непроверяемыми.
В Java исключения, список которых представлен ниже, используются наиболее часто, поэтому стоит описать каждый из них подробней:
Данные примеры представляют собой непроверяемые типы исключений Java. А вот так выглядят проверяемые:
Говоря о часто встречаемых исключениях нужно отметить, что их интерпретация в ходе разработки, может быть воспринята неправильно. Далее идёт небольшой список, поясняющий более подробно, когда может возникнуть непредвиденная ситуация.
NullPointerException. Самым первым случаем, когда возникает исключение, является обращение к ссылке на объект, которая равна null. Также это распространяется на методы нулевого экземпляра класса. NullPointerException может быть брошен и в случае получения длины массива равной null. Избежать таких ситуаций поможет периодическая проверка объектов на null.
ArrayIndexOutOfBoundsException. Любая программа не может существовать без использования массивов. Соответственно, частое обращение к ним может порождать и ошибки. Возникает исключение, когда разработчик пытается обратиться к элементу массива, который отсутствует в списке индексов. Например, запрашиваемое значение выше длины или меньше нуля. Очень часто появляется в результате того, что счёт в массиве начинается с нуля.
Обработка исключений Java — мощный инструмент среды, который значительно облегчает работу программиста и позволяет ему создавать чистый и лишенный ошибок код. От того, насколько плавно и стабильно функционирует приложение, зависит статус и репутация компании-разработчика.
Конечно, в более или менее простых программах отследить внештатные ситуации гораздо проще. А вот в больших автоматизированных комплексах на несколько сотен тысяч строк такое возможно только в результате проведения длительной отладки и тестирования.
За Java исключения, ошибки от которых возникают в некоторых приложениях, отдельные компании предлагают вознаграждение при их нахождении энтузиастами. Особенно ценятся те, которые вызывают нарушение политики безопасности программного комплекса.
Привет, Хабр! Представляю вашему вниманию перевод статьи Fixing 7 Common Java Exception Handling Mistakes автора Thorben Janssen.
Обработка исключения является одной из наиболее распространенных, но не обязательно одной из самых простых задач. Это все еще одна из часто обсуждаемых тем в опытных командах, и есть несколько передовых методов и распространенных ошибок, о которых вы должны знать.
Вот несколько вещей, которые следует избегать при обработке исключений в вашем приложении.
Public void doNotSpecifyException() throws Exception {
doSomething();
}
public void doSomething() throws NumberFormatException, IllegalArgumentException {
// do something
}
Но это не значит, что вы должны это сделать. Указание Exeption или Throwable делает почти невозможным правильное обращение с ними при вызове вашего метода.Единственная информация, которую получает вызывающий вами метод, заключается в том, что что-то может пойти не так. Но вы не делитесь какой-либо информацией о каких-либо исключительных событиях, которые могут произойти. Вы скрываете эту информацию за обобщенными причинами выброса исключений.Становится еще хуже, когда ваше приложение меняется со временем. Выброс обобщенных исключений скрывает все изменения исключений, которые вызывающий должен ожидать и обрабатывать. Это может привести к нескольким непредвиденным ошибкам, которые необходимо найти в тестовом примере вместо ошибки компилятора.
Public void specifySpecificExceptions() throws NumberFormatException, IllegalArgumentException { doSomething(); }
Это дает несколько преимуществ. Такой подход позволяет обрабатывать каждый класс исключений по-разному и не позволяет вам перехватывать исключения, которых вы не ожидали.
Но имейте в виду, что первый блок catch, который обрабатывает класс исключения или один из его супер-классов, поймает его. Поэтому сначала обязательно поймайте наиболее специфический класс. В противном случае ваши IDE покажут сообщение об ошибке или предупреждении о недостижимом блоке кода.
Try { doSomething(); } catch (NumberFormatException e) { // handle the NumberFormatException log.error(e); } catch (IllegalArgumentException e) { // handle the IllegalArgumentException log.error(e); }
1. У вас недостаточно информации о прецеденте, который хочет реализовать вызывающий объект вашего метода. Исключение может быть частью ожидаемого поведения и обрабатываться клиентом. В этом случае нет необходимости регистрировать его. Это добавит ложное сообщение об ошибке в файл журнала, который должен быть отфильтрован вашей операционной группой.
2. Сообщение журнала не предоставляет никакой информации, которая еще не является частью самого исключения. Его трассировка и трассировка стека должны содержать всю необходимую информацию об исключительном событии. Сообщение описывает это, а трассировка стека содержит подробную информацию о классе, методе и строке, в которой она произошла.
3. Вы можете регистрировать одно и то же исключение несколько раз, когда вы регистрируете его в каждом блоке catch, который его ловит. Это испортит статистику в вашем инструменте мониторинга и затрудняет чтение файла журнала для ваших операций и команды разработчиков.
Public void doEvenMore() { try { doMore(); } catch (NumberFormatException e) { // handle the NumberFormatException } catch (IllegalArgumentException e) { // handle the IllegalArgumentException } } public void doMore() throws NumberFormatException, IllegalArgumentException { doSomething(); } public void doSomething() throws NumberFormatException, IllegalArgumentException { // do something }
Они в основном работают как оператор Go To, потому что они отменяют выполнение блока кода и переходят к первому блоку catch, который обрабатывает исключение. Это делает код очень трудным для чтения.
Они не так эффективны, как общие структуры управления Java. Как видно из названия, вы должны использовать их только для исключительных событий, а JVM не оптимизирует их так же, как и другой код.Таким образом, лучше использовать правильные условия, чтобы разбить свои циклы или инструкции if-else, чтобы решить, какие блоки кода должны быть выполнены.
Когда вы создаете новое исключение, вы всегда должны устанавливать первоначальное исключение в качестве причины. В противном случае вы потеряете трассировку сообщения и стека, которые описывают исключительное событие, вызвавшее ваше исключение. Класс Exception и все его подклассы предоставляют несколько методов-конструкторов, которые принимают исходное исключение в качестве параметра и задают его как причину.
Public void doNotGeneralizeException() throws Exception {
try {
doSomething();
} catch (NumberFormatException e) {
throw new Exception(e);
} catch (IllegalArgumentException e) {
throw new Exception(e);
}
}
Как вы можете видеть в следующем фрагменте кода, даже если вы знаете, какие исключения может вызвать метод, вы не можете просто их поймать. Вам нужно поймать общий класс Exception и затем проверить тип его причины. Этот код не только громоздкий для реализации, но его также трудно читать. Становится еще хуже, если вы сочетаете этот подход с ошибкой 5. Это удаляет всю информацию об исключительном событии.
Try {
doNotGeneralizeException();
} catch (Exception e) {
if (e.getCause() instanceof NumberFormatException) {
log.error("NumberFormatException: " + e);
} else if (e.getCause() instanceof IllegalArgumentException) {
log.error("IllegalArgumentException: " + e);
} else {
log.error("Unexpected exception: " + e);
}
}
Итак, какой подход лучший?
Try { doSomething(); } catch (NumberFormatException e) { throw new MyBusinessException(e, ErrorCode.CONFIGURATION_ERROR); } catch (IllegalArgumentException e) { throw new MyBusinessException(e, ErrorCode.UNEXPECTED); }
Public void persistCustomer(Customer c) throws MyPersistenceException {
// persist a Customer
}
public void manageCustomer(Customer c) throws MyBusinessException {
// manage a Customer
try {
persistCustomer(c);
} catch (MyPersistenceException e) {
throw new MyBusinessException(e, e.getCode());
}
}
public void createCustomer(Customer c) throws MyApiException {
// create a Customer
try {
manageCustomer(c);
} catch (MyBusinessException e) {
throw new MyApiException(e, e.getCode());
}
}
Легко видеть, что эти дополнительные классы исключений не дают никаких преимуществ. Они просто вводят дополнительные слои, которые оборачивают исключение. И хотя было бы забавно обернуть подарок во множестве красочной бумаги, это не очень хороший подход к разработке программного обеспечения.
Поэтому будьте осторожны с количеством настраиваемых классов исключений, которые вы вводите. Вы всегда должны спрашивать себя, дает ли новый класс исключений дополнительную информацию или другие преимущества. В большинстве случаев для достижения этого вам не требуется более одного уровня пользовательских исключений.
Public void persistCustomer(Customer c) { // persist a Customer } public void manageCustomer(Customer c) throws MyBusinessException { // manage a Customer throw new MyBusinessException(e, e.getCode()); } public void createCustomer(Customer c) throws MyBusinessException { // create a Customer manageCustomer(c); }