Подробно про обработку исключений java
Содержание:
- Проверяемые исключения: throws, checked exceptions
- What is an Exception
- Обеспечение доступности данных об исключении при удаленном выполнении кода
- Ловим исключения
- try…catch…finally
- Присоединенные дочерние задачи и вложенные исключения AggregateException
- Обработка исключений[править]
- Создание классов исключений
- Обработка исключения
- LogicException
- Классификация исключений[править]
- Что называют исключением. Исключения в мире программирования
Проверяемые исключения: throws, checked exceptions
Все исключения в Java делятся на 2 категории — проверяемые (checked) и непроверяемые (unchecked).
Все исключения, унаследованные от классов и , считаются unchecked-исключениями, все остальные — checked-исключениями.
Важно!
Спустя 20 лет после введения проверяемых исключений, почти все Java-программисты считают это ошибкой. 95% всех исключений в популярных современных фреймворках — непроверяемые. Тот же язык C#, который чуть ли не скопировал Java подчистую, не стал добавлять checked-исключения.
В чем же основное отличие checked-исключений от unchecked?
К checked-исключениям есть дополнительные требования. Звучат они примерно так.
Требование 1
Если метод выбрасывает checked-исключение, он должен содержать тип этого исключения в своем заголовке (сигнатуре метода)
Чтобы все методы, которые вызывают данный метод, знали о том, что в нем может возникнуть такое «важное исключение»
Указывать checked-исключения надо после параметров метода после ключевого слова (не путать со ). Выглядит это примерно так:
Пример:
checked-исключение | unchecked-исключение |
---|---|
В примере справа наш код выкидывает unchecked-исключение — никаких дополнительных действий не нужно. В примере слева метод выкидывает checked-исключение, поэтому в сигнатуру метода добавили ключевое слово и указали тип исключения.
Если метод планирует выкидывать несколько checked-исключений, все их нужно указать после ключевого слова через запятую. Порядок неважен. Пример:
Требование 2
Если вы вызываете метод, у которого в сигнатуре прописаны checked-исключения, то вы не можете проигнорировать этот факт.
Вы должны либо перехватить все эти исключения, добавив блоки для каждого из них, либо добавить их в своего метода.
Мы как бы говорим себе: эти исключения настолько важные, что мы обязательно должны их перехватить. А если мы не знаем, как их перехватить, мы должны уведомить тех, кто будет вызывать наш метод, что в нем могут возникнуть такие исключения.
Пример:
Представим, что мы пишем метод, который должен создать мир, населенный людьми. Начальное количество человек передается в качестве параметра. Тогда мы должны добавить исключения, если людей слишком мало.
Создаем Землю | Примечание |
---|---|
Метод потенциально кидает два checked-исключения:
|
Вызов этого метода можно обработать 3 способами:
1. Не перехватываем возникающие исключения
Чаще всего это делается в случае, когда в методе не известно, как правильно обработать эту ситуацию.
Код | Примечание |
---|---|
Вызывающий метод не перехватывает исключения и вынужден информировать о них других: добавить их себе в |
2. Перехватывать часть исключений
Обрабатываем понятные ошибки, непонятные — прокидываем в вызывающий метод. Для этого нужно добавить их название в throws:
Код | Примечание |
---|---|
Вызывающий метод перехватывает только одно checked-исключение – , второе он должен добавить в свою сигнатуру: указать после слова |
3. Перехватываем все исключения
Если метод не прокидывает исключения вызывающему методу, вызывающий метод всегда будет уверен, что все выполнилось хорошо. И не сможет предпринять никаких действий, чтобы исправить ситуацию.
Код | Примечание |
---|---|
В этом методе перехватываются все ошибки. Вызывающий метод будет уверен, что все прошло отлично. |
What is an Exception
With PHP 5 came a new object oriented way of dealing with errors.
Exception handling is used to change the normal flow of the code execution if
a specified error (exceptional) condition occurs. This condition is called an
exception.
This is what normally happens when an exception is triggered:
- The current code state is saved
- The code execution will switch to a predefined (custom) exception handler function
- Depending on the situation, the handler may then resume the execution from the saved code state, terminate the script execution or continue the script from a different location in the code
We will show different error handling methods:
- Basic use of Exceptions
- Creating a custom exception handler
- Multiple exceptions
- Re-throwing an exception
- Setting a top level exception handler
Note: Exceptions should only be used with error conditions, and should not be used
to jump to another place in the code at a specified point.
Обеспечение доступности данных об исключении при удаленном выполнении кода
При создании пользовательских исключений следует обеспечить доступность метаданных исключений для удаленно исполняемого кода.
Например, для реализаций .NET, которые поддерживают домены приложений, могут возникать исключения для этих доменов. Предположим, что домен приложения А создает домен приложения В, который выполняет код, вызывающий исключение. Чтобы домен приложения A правильно перехватил и обработал исключение, он должен найти сборку, которая содержит исключение, порожденное доменом приложения B. Если домен приложения B порождает исключение, содержащееся в сборке в его базовой папке приложения, но не в базовой папке приложения домена A, то домен приложения A не сможет найти исключение и среда CLR породит исключение FileNotFoundException. Чтобы избежать такой ситуации, можно развернуть сборку, содержащую сведения об исключении, двумя способами:
-
Поместите эту сборку в общую базу приложения, совместно используемую обоими доменами приложений.
— или —
-
Если у этих доменов нет общей базы приложения, то подпишите сборку, содержащую сведения об исключении, строгим именем и разверните ее в глобальном кэше сборок.
Ловим исключения
Исключения можно «ловить». Это полезно в нескольких случаях. Иногда мы можем как-то отреагировать на неудачу: например, при ошибке скачивании файла по сети можно сделать паузу и повторить попытку. Для этого нам надо перехватить выброшенное функцией исключение.
Также, исключения обычно ловят на верхнем уровне программы в веб-приложениях для того, чтобы сделать свою страницу, информирующую об ошибке (так как PHP при непойманном исключении завершает программу, и пользователь видит пустую белую страницу в браузере, что плохо). В приложениях, запускаемых в командной строке, а не в браузере, обычно это не требуется, так как они не ориентированы на «обычных» пользователей.
Перехватывать исключения в теории можно двумя способами: неструктурно и структурно. Неструктурно — это когда мы задаем обработчик исключений в начале программы:
set_exception_handler(function (Exception $exception) { // Функция будет вызвана при возникновении исключения });
Этот способ ловит неперехваченные исключения любых видов во всей программе, его можно использовать для того, чтобы сделать свою страницу ошибки (об этом подробнее написано ниже). После срабатывания обработчика программа будет завершена, предотвратить это невозможно.
Структурная обработка исключений — это когда мы ловим только исключения определенных типов в опредленном месте кода. Она реализуется с помощью /:
try { // В try пишется код, в котором мы хотим перехватывать исключения $users = loadUsersFromFile(...); .... } catch (LoadUsersException $e) { // В catch мы указываем, исключения каких классов хотим ловить. // В данном случае мы ловим исключения класса LoadUsersException и его // наследников, то есть только те, которые выбрасывает наша функция // Блоков catch может быть несколько, для разных классов die("Ошибочка: {$e->getMessage()}\n"); }
В PHP5.5 и выше добавлен блок . Команды из этого блока будут выполнены после любого из блоков ( или ) — в случае если исключения не произойдет и в случае если оно произойдет.
Перехватывать абсолютно любые типы исключений — плохая идея, так как мы обычно хотим обрабатывать только определенные, «наши», типы ошибок и не знаем что делать с другими. Чтобы перехватывать только нужные нам исключения, надо сделать свой класс на основе встроенного в PHP :
class LoadUsersException extends Exception { }
Выкидывать его в
throw new LoadUsersException("Файл $file не существует");
И ловить в только наши исключения:
catch (LoadUsersException $e) { .... обрабатываем ошибку ... }
Мы можем рассматривать исключения как часть интерфейса функции (часть правил работы с этой функцией). Есть аргументы, которые мы даем на вход, есть результат, который она возвращает, и есть исключения которые она может выкинуть при ошибке. И использование исключений позволяет пользователю функции (тому кто ее вызвал) решить что делать в случае ошибки.
try…catch…finally
Подождите, это ещё не всё.
Конструкция может содержать ещё одну секцию: .
Если секция есть, то она выполняется в любом случае:
- после , если не было ошибок,
- после , если ошибки были.
Расширенный синтаксис выглядит следующим образом:
Попробуйте запустить такой код:
У кода есть два пути выполнения:
- Если вы ответите на вопрос «Сгенерировать ошибку?» утвердительно, то .
- Если ответите отрицательно, то .
Секцию часто используют, когда мы начали что-то делать и хотим завершить это вне зависимости от того, будет ошибка или нет.
Например, мы хотим измерить время, которое занимает функция чисел Фибоначчи . Естественно, мы можем начать измерения до того, как функция начнёт выполняться и закончить после. Но что делать, если при вызове функции возникла ошибка? В частности, реализация в коде ниже возвращает ошибку для отрицательных и для нецелых чисел.
Секция отлично подходит для завершения измерений несмотря ни на что.
Здесь гарантирует, что время будет измерено корректно в обеих ситуациях – и в случае успешного завершения и в случае ошибки:
Вы можете это проверить, запустив этот код и введя в – код завершится нормально, выполнится после . А затем введите – незамедлительно произойдёт ошибка, выполнение займёт . Оба измерения выполняются корректно.
Другими словами, неважно как завершилась функция: через или. Секция срабатывает в обоих случаях
Переменные внутри локальны
Обратите внимание, что переменные и в коде выше объявлены до. Если переменную объявить в блоке, например, в , то она не будет доступна после него
Если переменную объявить в блоке, например, в , то она не будет доступна после него.
и
Блок срабатывает при любом выходе из , в том числе и .
В примере ниже из происходит , но получает управление до того, как контроль возвращается во внешний код.
Конструкция без секции также полезна. Мы применяем её, когда не хотим здесь обрабатывать ошибки (пусть выпадут), но хотим быть уверены, что начатые процессы завершились.
В приведённом выше коде ошибка всегда выпадает наружу, потому что тут нет блока . Но отрабатывает до того, как поток управления выйдет из функции.
Присоединенные дочерние задачи и вложенные исключения AggregateException
Если задача имеет присоединенную дочернюю задачу, которая создает исключение, это исключение заключается в AggregateException перед распространением в родительскую задачу, которая заключает его в собственное исключение AggregateException перед распространением обратно в вызывающий поток. В таких случаях свойство InnerExceptions исключения AggregateException, перехватываемого в методе Task.Wait, WaitAny или WaitAll, содержит один или несколько экземпляров AggregateException, а не исходные исключения, которые вызвали сбой. Чтобы не перебирать все вложенные исключения AggregateException, можно с помощью метода Flatten удалить все вложенные исключения AggregateException, чтобы свойство AggregateException.InnerExceptions содержало только исходные исключения. В следующем примере вложенные экземпляры AggregateException сглаживаются и обрабатываются всего в одном цикле.
Вы также можете использовать метод AggregateException.Flatten, чтобы повторно создать в одном экземпляре AggregateException все вложенные исключения, полученные в нескольких экземплярах AggregateException от нескольких задач, как показано в следующем примере.
Обработка исключений[править]
Чтобы сгенерировать исключение используется ключевое слово . Как и любой объект в Java, исключения создаются с помощью .
(t == ) { NullPointerException(); }
Есть два стандартных конструктора для всех исключений: первый — конструктор по умолчанию, второй принимает строковый аргумент, поэтому можно поместить подходящую информацию в исключение.
Возможна ситуация, когда одно исключение становится причиной другого. Для этого существует механизм exception chaining. Практически у каждого класса исключения есть конструктор, принимающий в качестве параметра – причину исключительной ситуации. Если же такого конструктора нет, то у есть метод , который можно вызвать один раз, и передать ему исключение-причину.
Как и было сказано раньше, определение метода должно содержать список всех проверяемых исключений, которые метод может бросить. Также можно написать более общий класс, среди наследников которого есть эти исключения.
f() InterruptedException, IOException {
try-catch-finallyправить
Код, который может бросить исключения оборачивается в -блок, после которого идут блоки и (Один из них может быть опущен).
{ }
Сразу после блока проверки следуют обработчики исключений, которые объявляются ключевым словом catch.
{ } (Type1 id1) { } (Type2 id2) { }
-блоки обрабатывают исключения, указанные в качестве аргумента. Тип аргумента должен быть классом, унаследованного от , или самим . Блок выполняется, если тип брошенного исключения является наследником типа аргумента и если это исключение не было обработано предыдущими блоками.
Код из блока выполнится в любом случае: при нормальном выходе из , после обработки исключения или при выходе по команде .
NB: Если JVM выйдет во время выполнения кода из или , то -блок может не выполниться. Также, например, если поток выполняющий или код остановлен, то блок может не выполниться, даже если приложение продолжает работать.
Блок удобен для закрытия файлов и освобождения любых других ресурсов. Код в блоке должен быть максимально простым. Если внутри блока будет брошено какое-либо исключение или просто встретится оператор , брошенное в блоке исключение (если таковое было брошено) будет забыто.
java.io.IOException; ExceptionTest { main(String[] args) { { { Exception(); } { IOException(); } } (IOException ex) { System..println(ex.getMessage()); } (Exception ex) { System..println(ex.getMessage()); } } }
После того, как было брошено первое исключение — — будет выполнен блок , в котором будет брошено исключение , именно оно будет поймано и обработано. Результатом его выполнения будет вывод в консоль . Исходное исключение теряется.
Обработка исключений, вызвавших завершение потокаправить
При использовании нескольких потоков бывают ситуации, когда поток завершается из-за исключения. Для того, чтобы определить с каким именно, начиная с версии Java 5 существует интерфейс . Его реализацию можно установить нужному потоку с помощью метода . Можно также установить обработчик по умолчанию с помощью статического метода .
Интерфейс имеет единственный метод , в который передается экземпляр потока, завершившегося исключением, и экземпляр самого исключения. Когда поток завершается из-за непойманного исключения, JVM запрашивает у потока , используя метод , и вызвает метод обработчика – . Все исключения, брошенные этим методом, игнорируются JVM.
Информация об исключенияхправить
- . Этот метод возвращает строку, которая была первым параметром при создании исключения;
- возвращает исключение, которое стало причиной текущего исключения;
- печатает stack trace, который содержит информацию, с помощью которой можно определить причину исключения и место, где оно было брошено.
Exception in thread "main" java.lang.IllegalStateException: A book has a null property at com.example.myproject.Author.getBookIds(Author.java:38) at com.example.myproject.Bootstrap.main(Bootstrap.java:14) Caused by: java.lang.NullPointerException at com.example.myproject.Book.getId(Book.java:22) at com.example.myproject.Author.getBookIds(Author.java:35)
Все методы выводятся в обратном порядке вызовов. В примере исключение было брошено в методе , который был вызван в . «Caused by» означает, что исключение является причиной .
Создание классов исключений
Последнее обновление: 23.10.2018
Если нас не устраивают встроенные типы исключений, то мы можем создать свои типы. Базовым классом
для всех исключений является класс Exception, соответственно для создания своих типов мы можем унаследовать данный класс.
Допустим, у нас в программе будет ограничение по возрасту:
class Program { static void Main(string[] args) { try { Person p = new Person { Name = "Tom", Age = 17 }; } catch (Exception ex) { Console.WriteLine($"Ошибка: {ex.Message}"); } Console.Read(); } } class Person { private int age; public string Name { get; set; } public int Age { get { return age; } set { if (value < 18) { throw new Exception("Лицам до 18 регистрация запрещена"); } else { age = value; } } } }
В классе Person при установке возраста происходит проверка, и если возраст меньше 18, то выбрасывается исключение. Класс Exception принимает
в конструкторе в качестве параметра строку, которое затем передается в его свойство Message.
Но иногда удобнее использовать свои классы исключений. Например, в какой-то ситуации мы хотим обработать определенным образом только те исключения,
которые относятся к классу Person. Для этих целей мы можем сделать специальный класс PersonException:
class PersonException : Exception { public PersonException(string message) : base(message) { } }
По сути класс кроме пустого конструктора ничего не имеет, и то в конструкторе мы просто обращаемся к конструктору базового класса
Exception, передавая в него строку message. Но теперь мы можем изменить класс Person, чтобы он выбрасывал исключение именно этого типа и
соответственно в основной программе обрабатывать это исключение:
class Program { static void Main(string[] args) { try { Person p = new Person { Name = "Tom", Age = 17 }; } catch (PersonException ex) { Console.WriteLine("Ошибка: " + ex.Message); } Console.Read(); } } class Person { private int age; public int Age { get { return age; } set { if (value < 18) throw new PersonException("Лицам до 18 регистрация запрещена"); else age = value; } } }
Однако необязательно наследовать свой класс исключений именно от типа Exception, можно взять какой-нибудь другой
производный тип. Например, в данном случае мы можем взять тип ArgumentException, который представляет исключение,
генерируемое в результате передачи аргументу метода некорректного значения:
class PersonException : ArgumentException { public PersonException(string message) : base(message) { } }
Каждый тип исключений может определять какие-то свои свойства. Например, в данном случае мы можем определить в классе свойство для хранения устанавливаемого значения:
class PersonException : ArgumentException { public int Value { get;} public PersonException(string message, int val) : base(message) { Value = val; } }
В конструкторе класса мы устанавливаем это свойство и при обработке исключения мы его можем получить:
class Person { public string Name { get; set; } private int age; public int Age { get { return age; } set { if (value < 18) throw new PersonException("Лицам до 18 регистрация запрещена", value); else age = value; } } } class Program { static void Main(string[] args) { try { Person p = new Person { Name = "Tom", Age = 13 }; } catch (PersonException ex) { Console.WriteLine($"Ошибка: {ex.Message}"); Console.WriteLine($"Некорректное значение: {ex.Value}"); } Console.Read(); } }
НазадВперед
Обработка исключения
Обработка исключений в Java подразумевает создание блоков кода и производится в программе посредством конструкций try{}finally{}, try{}catch, try{}catch{}finally.
В процессе возбуждения исключения в try обработчик исключения ищется в блоке catch, который следует за try. При этом если в catch присутствует обработчик данного вида исключения, происходит передача управления ему. Если же нет, JVM осуществляет поиск обработчика данного типа исключения, используя для этого цепочку вызова методов. И так происходит до тех пор, пока не находится подходящий catch. После того, как блок catch выполнится, управление переходит в необязательный блок finally. Если подходящий блок catch найден не будет, Java Virtual Machine остановит выполнение программы, выведя стек вызовов методов под названием stack trace. Причём перед этим выполнится код блока finally при наличии такового.
Рассмотрим практический пример обработки исключений:
public class Print { void print(String s) { if (s == null) { throw new NullPointerException("Exception: s is null!"); } System.out.println("Inside method print: " + s); } public static void main(String[] args) { Print print = new Print(); List list= Arrays.asList("first step", null, "second step"); for (String slist) { try { print.print(s); } catch (NullPointerException e) { System.out.println(e.getMessage()); System.out.println("Exception was processed. Program continues"); } finally { System.out.println("Inside bloсk finally"); } System.out.println("Go program...."); System.out.println("-----------------"); } } }
А теперь глянем на результаты работы метода main:
Inside method print first step Inside bloсk finally Go program.... ----------------- Exception: s is null! Exception was processed. Program continues Inside bloсk finally Go program.... ----------------- Inside method print second step Inside bloсk finally Go program.... -----------------
Блок finally чаще всего используют, чтобы закрыть открытые в try потоки либо освободить ресурсы. Но при написании программы уследить за закрытием всех ресурсов возможно не всегда. Чтобы облегчить жизнь разработчикам Java, была предложена конструкция try-with-resources, автоматически закрывающая ресурсы, открытые в try. Используя try-with-resources, мы можем переписать наш первый пример следующим образом:
public String input() throws MyException { String s = null; try(BufferedReader reader = new BufferedReader(new InputStreamReader(System.in))){ s = reader.readLine(); } catch (IOException e) { System.out.println(e.getMessage()); } if (s.equals("")){ throw new MyException ("String can not be empty!"); } return s; }
А благодаря появившимся возможностям Java начиная с седьмой версии, мы можем ещё и объединять в одном блоке перехват разнотипных исключений, делая код компактнее и читабельнее:
public String input() { String s = null; try (BufferedReader reader = new BufferedReader(new InputStreamReader(System.in))) { s = reader.readLine(); if (s.equals("")) { throw new MyException("String can not be empty!"); } } catch (IOException | MyException e) { System.out.println(e.getMessage()); } return s; }
LogicException
Используется, когда ваш код возвращает значение, которое не должен возвращать. Часто вызывается при разных багах в коде. Потомки этого класса используются в более специализированных ситуациях. Если ни одна из них не подходит под ваш случай, можно использовать LogicException.
BadFunctionCallException
Используется, когда вызываемой функции физически не существует или когда в вызове используется неверное число аргументов. Редко бывает нужно.
BadMethodCallException
Подкласс BadFunctionCallException. Аналогично ему используется для методов, которые не существют или которым передано неверное число параметров. Всегда используйте внутри __call(), в основном для этого оно и применяется.
Вот пример использования этих двух исключений:
// Для метода в __call class Foo { public function __call($method, $args) { switch ($method) { case 'someExistentClass': /* do something positive... */ break; default: throw new BadMethodCallException('Метод ' . $method . ' не может быть вызван'); } } } // процедурный подход function foo($arg1, $arg2) { $func = 'do' . $arg2; if (!is_callable($func)) { throw new BadFunctionCallException('Функция ' . $func . ' не может быть вызвана'); } }
DomainException
Если в коде подразумеваются некие ограничения для значений, то это исключение можно вызывать, когда значение выходит за эти ограничения. Например, у вас дни недели обозначаются числами от 1 до 7, а ваш метод получает внезапно на вход 0 или 9, или, скажем, вы ожидаете число, обозначающее количество зрителей в зале, а получаете отрицательное значени. Вот в таких случаях и вызывается DomainException. Также можно использовать для разных проверок параметров, когда параметры нужных типов, но при этом не проходят проверку на значение. Например,
if ($a>5){ throw new DomainException ("a должно быть меньше 5"); }
InvalidArgumentException
Вызываем, когда ожидаемые аргументы в функции/методе некорректно сформированы. Например, ожидается целое число, а на входе строка или ожидается GET, а пришел POST и т.п.
Пример:
public function foo($number) { if(!is_numeric($number)) { throw new InvalidArgumentException('На входе ожидалось число!'); } }
Вызываем, если длина чего-то слишком велика или мала. Например, имя файла слишком короткое или длина массива слишком большая.
Классификация исключений[править]
Класс Java описывает все, что может быть брошено как исключение. Наследеники — и — основные типы исключений. Также , унаследованный от , является существенным классом.
Иерархия стандартных исключений
Проверяемые исключенияправить
Наследники класса (кроме наслеников ) являются проверяемыми исключениями(checked exception). Как правило, это ошибки, возникшие по вине внешних обстоятельств или пользователя приложения – неправильно указали имя файла, например. Эти исключения должны обрабатываться в ходе работы программы, поэтому компилятор проверяет наличие обработчика или явного описания тех типов исключений, которые могут быть сгенерированы некоторым методом.
Все исключения, кроме классов и и их наследников, являются проверяемыми.
Errorправить
Класс и его подклассы предназначены для системных ошибок. Свои собственные классы-наследники для писать (за очень редкими исключениями) не нужно. Как правило, это действительно фатальные ошибки, пытаться обработать которые довольно бессмысленно (например ).
RuntimeExceptionправить
Эти исключения обычно возникают в результате ошибок программирования, такие как ошибки разработчика или неверное использование интерфейса приложения. Например, в случае выхода за границы массива метод бросит . Такие ошибки могут быть в любом месте программы, поэтому компилятор не требует указывать runtime исключения в объявлении метода. Теоретически приложение может поймать это исключение, но разумнее исправить ошибку.
Что называют исключением. Исключения в мире программирования
В программировании исключением называют возникновение ошибки (ошибок) и различных непредвиденных ситуаций в процессе выполнения программы. Исключения могут появляться как в итоге неправильных действий юзера, так и из-за потери сетевого соединения с сервером, отсутствии нужного ресурса на диске и т. п. Также среди причин исключений — ошибки программирования либо неверное использование API.
При этом в отличие от «человеческого мира», программное приложение должно чётко понимать, как поступать в подобной ситуации. И вот как раз для этого в Java и существует механизм исключений (exception).