Найти тему
ZDG

Программирование: Зачем нужны исключения #2

Оглавление

Предыдущая часть:

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

Читайте также:

Классы исключений

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

exception = new Exception();

И в общем-то, пока мы его не бросим с помощью throw, ничего не произойдёт.

Однако Exception – не единственный класс, который можно бросать. Во-первых, мы можем отнаследовать от него свой класс:

class MyException extends Exception { ... }

и бросать его:

throw new MyException();

Во-вторых, сам класс Exception тоже от чего-то отнаследован, либо поддерживает определённый интерфейс.

Давайте посмотрим на примере Java и PHP.

В PHP есть интерфейс, а в Java есть класс под названием Throwable, то есть "бросаемое". Это базовый класс/интерфейс для объектов, которые можно бросать. От Throwable затем наследуются классы Exception и Error. То есть мы можем бросить как new Exception(), так и new Error(). В чём разница?

В общем-то различие лишь смысловое. Exception – то, что мы можем поймать и обработать. Error – то, что нам лучше не ловить и не обрабатывать, так как его причины более серьёзные и от нас не зависят. Как правило, Error бросают внутренние механизмы среды, в которой выполняется программа. Например, если в системе кончилась память, то мы получим OutOfMemoryError.

В общем, мы имеем дело c целым семейством классов и подклассов, отнаследованных от Exception и Error, или поддерживающих интерфейс Throwable. Это даёт нам дополнительные возможности в тонкой настройке логики программы.

Скажем, одна из наших функций может бросать несколько типов исключений. Если возникла ситуация "файл не найден", она бросит исключение типа FileNotFoundException. Если ей прислали неверные параметры, она бросит исключение типа InvalidArgumentException. А если не удалось соединиться с сайтом по протоколу HTTP, она бросит HttpErrorException.

Все эти исключения мы можем поймать с помощью одной ловушки try–catch:

try { test(); }
catch(Exception ex) { ... }

Обратите внимание, что в catch() в скобках написано Exception ex. Переменная-аргумент ex это само исключение, которое мы поймали. А Exception – его тип. Таким образом мы указываем, какой тип исключения мы хотим ловить.

Так как FileNotFoundException, InvalidArgumentException и HttpErrorException все отнаследованы от Exception, то благодаря полиморфизму они будут отловлены.

Но если мы в catch() напишем так:

catch(FileNotFoundException ex) { ... }

То теперь будем ловить только конкретный тип: FileNotFoundException (и все классы, отнаследованные от него, конечно)

Что же произойдёт с другими типами исключений? Они не будут пойманы этой ловушкой и, как обычно, продолжат свой путь на верхние уровни. Где мы тоже можем их поймать или не поймать.

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

Это даёт нам дополнительную гибкость в обработке исключений.

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

Это можно сделать с помощью нескольких блоков catch{}:

try { test(); }
catch(FileNotFoundException ex) { ... }
catch(InvalidArgumentException ex) { ... }
catch(HttpErrorException ex) { ... }

В крайнем случае можно проверять тип исключения:

Здесь мы одним блоком catch{} ловим любые типы исключений. Затем проверяем тип и делаем обработку, если этот тип мы поддерживаем. Ну а если ни один тип не совпал, то бросаем это исключение ещё раз, ведь мы его поймали, и значит нужно его отправить снова в полёт.

Свойства и методы исключений

Как у любого объекта, у исключения есть свойства и методы. Ими можно воспользоваться, чтобы получить какую-то полезную информацию. Реализация может различаться в зависимости от языка, но в целом всё приблизительно похоже, поэтому посмотрим на примере Java:

Exception.getMessage()

Метод, который возвращает сообщение об ошибке. Мы можем показать это сообщение на экране, записать в лог и т.п.:

catch(Exception ex) { write_log(ex.getMessage()); }

Когды мы создаём своё исключение, то можем задать для него сообщение об ошибке, например:

if (a != b) throw new Exception("a != b");

Exception.getStackTrace()

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

Exception.toString()

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

В принципе это основное, что нам нужно от исключения.

Ну и закончим этот краткий обзор ключевым словом

finally

В структуре блоков try–catch может присутствовать ещё один, который называется finally:

try { ... }
catch(Exception ex) { ... }
finally { ... }

Блок finally{} выполняется последним после try{} или catch{}, То есть, если в try{} не случилось исключения, то catch{} не выполняется, но finally{} выполняется. Если же исключение случилось, то выполняется catch{}, а затем всё равно выполняется finally{}. Это завершающий блок, его присуствие не обязательно, но желательно в некоторых случаях, когда нужно привести дела в порядок после всего.

Отметим одну особенность блока finally{}, на которой прокалываются даже программисты, которые поднаторели в try–catch. Он выполняется не просто всегда, а ВСЕГДА. Посмотрим на такой пример:

a = 1;
try { test(); }
catch(Exception ex) { return a; }
finally { a = 0; }


Переменная
a изначально равна 1. Если случится исключение, блок catch{} выполнит return a, то есть вернёт из функции значение a = 1.

Совершенно логично предположить, что если в catch{} случился return, то блок finally{} не выполнится. Ведь мы уже вышли из функции. Но нет. Блок finally{} выполняется в любом случае. То есть перед return будет выполнено a = 0, и следовательно return a вернёт не 1, а 0.

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

https://www.techbooky.com/hsa-developers-heterogeneous-computing-masses/
https://www.techbooky.com/hsa-developers-heterogeneous-computing-masses/

Наука
7 млн интересуются