Java как пробросить exception дальше

Содержание
  1. Исключения в Java, Часть I (try-catch-finally)
  2. 1. Ключевые слова: try, catch, finally, throw, throws
  3. 2. Почему используем System.err, а не System.out
  4. 3. Компилятор требует вернуть результат (или требует молчать)
  5. 4. Нелокальная передача управления (nonlocal control transfer)
  6. 5. try + catch (catch — полиморфен)
  7. 6. try + catch + catch + .
  8. 7. try + finally
  9. 8. try + catch + finally
  10. 9. Вложенные try + catch + finally
  11. Контакты
  12. Исправляем 7 распространенных ошибок обработки исключений в Java
  13. Ошибка 1: объявление java.lang.Exception или java.lang.Throwable
  14. Используйте конкретные классы
  15. Ошибка 2: перехват обобщенных исключений
  16. Ошибка 3: Логирование и проброс исключений
  17. Регистрируйте исключение там, где вы его обрабатываете
  18. Ошибка 4: использование исключений для управления потоком
  19. Ошибка 5: удалить причину возникновения исключения
  20. Ошибка 6: Обобщение исключений
  21. Будьте конкретны и сохраняйте причину возникновения исключения.
  22. Ошибка 7: добавление ненужных преобразований исключений
  23. Обязательно добавьте информацию

Исключения в Java, Часть I (try-catch-finally)

Это первая часть статьи, посвященной такому языковому механизму Java как исключения (вторая (checked/unchecked) вот). Она имеет вводный характер и рассчитана на начинающих разработчиков или тех, кто только приступает к изучению языка.

Также я веду курс «Scala for Java Developers» на платформе для онлайн-образования udemy.com (аналог Coursera/EdX).

1. Ключевые слова: try, catch, finally, throw, throws

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

  • try
  • catch
  • finally
  • throw
  • throws

«Магия» (т.е. некоторое поведение никак не отраженное в исходном коде и потому неповторяемое пользователем) исключений #1 заключается в том, что catch, throw, throws можно использовать исключительно с java.lang.Throwable или его потомками.

throws:
Годится

catch:
Годится

throw:
Годится

Кроме того, throw требуется не-null аргумент, иначе NullPointerException в момент выполнения

throw и new — это две независимых операции. В следующем коде мы независимо создаем объект исключения и «бросаем» его

Однако, попробуйте проанализировать вот это

2. Почему используем System.err, а не System.out

System.out — buffered-поток вывода, а System.err — нет. Таким образом вывод может быть как таким

Так и вот таким (err обогнало out при выводе в консоль)

Давайте это нарисуем

когда Вы пишете в System.err — ваше сообщение тут же выводится на консоль, но когда пишете в System.out, то оно может на какое-то время быть буферизированно. Stacktrace необработанного исключение выводится через System.err, что позволяет им обгонять «обычные» сообщения.

3. Компилятор требует вернуть результат (или требует молчать)

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

вот так не пройдет (другой тип)

Вот так не выйдет — нет возврата

и вот так не пройдет (компилятор не может удостовериться, что возврат будет)

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

Из-забавного, можно ничего не возвращать, а «повесить метод»

Тут в d никогда ничего не будет присвоено, так как метод sqr повисает

Компилятор пропустит «вилку» (таки берем в квадрат ИЛИ висим)

Но механизм исключений позволяет НИЧЕГО НЕ ВОЗВРАЩАТЬ!

Итак, у нас есть ТРИ варианта для компилятора

Но КАКОЙ ЖЕ double вернет функция, бросающая RuntimeException?
А НИКАКОЙ!

Подытожим: бросаемое исключение — это дополнительный возвращаемый тип. Если ваш метод объявил, что возвращает double, но у вас нет double — можете бросить исключение. Если ваш метод объявил, что ничего не возвращает (void), но у вам таки есть что сказать — можете бросить исключение.

Давайте рассмотрим некоторый пример из практики.

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

важно, что задание звучит именно так, в терминах предметной области — «вычислить площадь прямоугольника», а не в терминах решения «перемножить два числа»:

Вопрос: что делать, если мы обнаружили, что хотя бы один из аргументов — отрицательное число?
Если просто умножить, то мы пропустили ошибочные данные дальше. Что еще хуже, возможно, мы «исправили ситуацию» — сказали что площадь прямоугольника с двумя отрицательными сторонами -10 и -20 = 200.

Мы не можем ничего не вернуть

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

Можно вернуть специальное значение, показывающее, что что-то не так (error code), но кто гарантирует, что его прочитают, а не просто воспользуются им?

Можем, конечно, целиком остановить виртуальную машину

Но «правильный путь» таков: если обнаружили возможное некорректное поведение, то
1. Вычисления остановить, сгенерировать сообщение-поломку, которое трудно игнорировать, предоставить пользователю информацию о причине, предоставить пользователю возможность все починить (загрузить белье назад и повторно нажать кнопку старт)

4. Нелокальная передача управления (nonlocal control transfer)

Механизм исключительных ситуация (исключений) — это механизм НЕЛОКАЛЬНОЙ ПЕРЕДАЧИ УПРАВЛЕНИЯ.
Что под этим имеется в виду?
Программа, в ходе своего выполнения (точнее исполнения инструкций в рамках отдельного потока), оперирует стеком («стопкой») фреймов. Передача управления осуществляется либо в рамках одного фрейма

и другие операторы.

Либо передача управления происходит в «стопке» фреймов между СОСЕДНИМИ фреймами

  • вызов метода: создаем новый фрейм, помещаем его на верхушку стека и переходим в него
  • выход из метода: возвращаемся к предыдущему фрейму (через return или просто кончились инструкции в методе)
Читайте также:  Soundmax sm ccr3706 прошивка

return — выходим из ОДНОГО фрейма (из фрейма #4(метод h()))

throw — выходим из ВСЕХ фреймов

При помощи catch мы можем остановить летящее исключение (причина, по которой мы автоматически покидаем фреймы).
Останавливаем через 3 фрейма, пролетаем фрейм #4(метод h()) + пролетаем фрейм #3(метод g()) + фрейм #2(метод f())

Обратите внимание, стандартный сценарий работы был восстановлен в методе main() (фрейм #1)

Останавливаем через 2 фрейма, пролетаем фрейм #4(метод h()) + пролетаем фрейм #3(метод g())

Останавливаем через 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

exit() vs halt()
javadoc: java.lang.Runtime#halt(int status)
… Unlike the exit method, this method does not cause shutdown hooks to be started and does not run uninvoked finalizers if finalization-on-exit has been enabled. If the shutdown sequence has already been initiated then this method does not wait for any running shutdown hooks or finalizers to finish their work.

Однако 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

Читайте также:  Failure in ssl library protocol error

Суть в том, что 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-канале, возможно, видео канала лучше систематизировано в этой статье.

Мой метод обучения состоит в том, что я

  1. показываю различные варианты применения
  2. строю усложняющуюся последовательность примеров по каждому варианту
  3. объясняю логику двигавшую авторами (по мере возможности)
  4. даю большое количество тестов (50-100) всесторонне проверяющее понимание и демонстрирующих различные комбинации
  5. даю лабораторные для самостоятельной работы

Данная статье следует пунктам #1 (различные варианты) и #2(последовательность примеров по каждому варианту).

Источник

Исправляем 7 распространенных ошибок обработки исключений в Java

Привет, Хабр! Представляю вашему вниманию перевод статьи Fixing 7 Common Java Exception Handling Mistakes автора Thorben Janssen.

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

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

Ошибка 1: объявление java.lang.Exception или java.lang.Throwable

Как вы уже знаете, вам нужно либо объявить, либо обработать проверяемое исключение. Но проверяемые исключения — это не единственные, которые вы можете указать. Вы можете использовать любой подкласс java.lang.Throwable в предложении throws. Таким образом, вместо указания двух разных исключений, которые выбрасывает следующий фрагмент кода, вы можете просто использовать исключение java.lang.Exception в предложении throws.

Но это не значит, что вы должны это сделать. Указание Exeption или Throwable делает почти невозможным правильное обращение с ними при вызове вашего метода.Единственная информация, которую получает вызывающий вами метод, заключается в том, что что-то может пойти не так. Но вы не делитесь какой-либо информацией о каких-либо исключительных событиях, которые могут произойти. Вы скрываете эту информацию за обобщенными причинами выброса исключений.Становится еще хуже, когда ваше приложение меняется со временем. Выброс обобщенных исключений скрывает все изменения исключений, которые вызывающий должен ожидать и обрабатывать. Это может привести к нескольким непредвиденным ошибкам, которые необходимо найти в тестовом примере вместо ошибки компилятора.

Используйте конкретные классы

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

Ошибка 2: перехват обобщенных исключений

Серьезность этой ошибки зависит от того, какой программный компонент вы реализуете, и где вы обнаруживаете исключение. Возможно, было бы хорошо поймать java.lang.Exception в основном методе вашего приложения Java SE. Но вы должны предпочесть поймать определенные исключения, если вы реализуете библиотеку или работаете над более глубокими слоями вашего приложения.

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

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

Ошибка 3: Логирование и проброс исключений

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

Читайте также:  Vontar z8 arc прошивка

1. У вас недостаточно информации о прецеденте, который хочет реализовать вызывающий объект вашего метода. Исключение может быть частью ожидаемого поведения и обрабатываться клиентом. В этом случае нет необходимости регистрировать его. Это добавит ложное сообщение об ошибке в файл журнала, который должен быть отфильтрован вашей операционной группой.

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

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

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

Таким образом, лучше всего регистрировать исключение тогда, когда вы его обрабатываете. Как в следующем фрагменте кода. Метод doSomething генерирует исключение. Метод doMore просто указывает его, потому что у разработчика недостаточно информации для его обработки. Затем он обрабатывается в методе doEvenMore, который также записывает сообщение журнала.

Ошибка 4: использование исключений для управления потоком

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

Они в основном работают как оператор Go To, потому что они отменяют выполнение блока кода и переходят к первому блоку catch, который обрабатывает исключение. Это делает код очень трудным для чтения.

Они не так эффективны, как общие структуры управления Java. Как видно из названия, вы должны использовать их только для исключительных событий, а JVM не оптимизирует их так же, как и другой код.Таким образом, лучше использовать правильные условия, чтобы разбить свои циклы или инструкции if-else, чтобы решить, какие блоки кода должны быть выполнены.

Ошибка 5: удалить причину возникновения исключения

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

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

Ошибка 6: Обобщение исключений

Когда вы обобщаете исключение, вы ловите конкретный, например, NumberFormatException, и вместо этого генерируете неспецифическое java.lang.Exception. Это похоже, но даже хуже, чем первая ошибка, которую я описал в этой статье. Он не только скрывает информацию о конкретном случае ошибки на вашем API, но также затрудняет доступ.

Как вы можете видеть в следующем фрагменте кода, даже если вы знаете, какие исключения может вызвать метод, вы не можете просто их поймать. Вам нужно поймать общий класс Exception и затем проверить тип его причины. Этот код не только громоздкий для реализации, но его также трудно читать. Становится еще хуже, если вы сочетаете этот подход с ошибкой 5. Это удаляет всю информацию об исключительном событии.

Итак, какой подход лучший?

Будьте конкретны и сохраняйте причину возникновения исключения.

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

Ошибка 7: добавление ненужных преобразований исключений

Как я уже объяснял ранее, может быть полезно обернуть исключения в пользовательские, если вы установите исходное исключение в качестве причины. Но некоторые архитекторы переусердствуют и вводят специальный класс исключений для каждого архитектурного уровня. Таким образом, они улавливают исключение в уровне персистентности и переносят его в MyPersistenceException. Бизнес-уровень ловит и обертывает его в MyBusinessException, и это продолжается до тех пор, пока оно не достигнет уровня API или не будет обработано.

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

Обязательно добавьте информацию

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

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

Источник

Smartadm.ru
Adblock
detector