I’m trying to learn more about basic Java and the different types of Throwables, can someone let me know the differences between Exceptions and Errors?
Termininja
6,44212 gold badges46 silver badges49 bronze badges
asked May 26, 2009 at 19:39
Errors should not be caught or handled (except in the rarest of cases). Exceptions are the bread and butter of exception handling. The Javadoc explains it well:
An Error is a subclass of Throwable that indicates serious problems that a
reasonable application should not try to catch. Most such errors are abnormal
conditions.
Look at a few of the subclasses of Error
, taking some of their JavaDoc comments:
AnnotationFormatError
— Thrown when the annotation parser attempts to read an annotation from a class file and determines that the annotation is malformed.AssertionError
— Thrown to indicate that an assertion has failed.LinkageError
— Subclasses of LinkageError indicate that a class has some dependency on another class; however, the latter class has incompatibly changed after the compilation of the former class.VirtualMachineError
— Thrown to indicate that the Java Virtual Machine is broken or has run out of resources necessary for it to continue operating.
There are really three important subcategories of Throwable
:
Error
— Something severe enough has gone wrong the most applications should crash rather than try to handle the problem,- Unchecked Exception (aka
RuntimeException
) — Very often a programming error such as aNullPointerException
or an illegal argument. Applications can sometimes handle or recover from thisThrowable
category — or at least catch it at the Thread’srun()
method, log the complaint, and continue running. - Checked Exception (aka Everything else) — Applications are expected to be able to catch and meaningfully do something with the rest, such as
FileNotFoundException
andTimeoutException
…
answered May 26, 2009 at 19:43
EddieEddie
53.5k22 gold badges124 silver badges144 bronze badges
2
Errors tend to signal the end of your application as you know it. It typically cannot be recovered from and should cause your VM to exit. Catching them should not be done except to possibly log or display and appropriate message before exiting.
Example:
OutOfMemoryError — Not much you can do as your program can no longer run.
Exceptions are often recoverable and even when not, they generally just mean an attempted operation failed, but your program can still carry on.
Example:
IllegalArgumentException — Passed invalid data to a method so that method call failed, but it does not affect future operations.
These are simplistic examples, and there is another wealth of information on just Exceptions alone.
answered May 26, 2009 at 19:47
RobinRobin
23.9k4 gold badges49 silver badges58 bronze badges
1
Errors —
Error
s in java are of typejava.lang.Error
.- All errors in java are unchecked type.
Error
s happen at run time. They will not be known to compiler.- It is impossible to recover from errors.
Error
s are mostly caused by the environment in which application is running.- Examples :
java.lang.StackOverflowError
,java.lang.OutOfMemoryError
Exceptions —
Exception
s in java are of typejava.lang.Exception
.Exception
s include both checked as well as unchecked type.- Checked exceptions are known to compiler where as unchecked exceptions are not known to compiler because they occur at run time.
- You can recover from exceptions by handling them through
try-catch
blocks. Exception
s are mainly caused by the application itself.- Examples : Checked Exceptions :
SQLException
,IOException
Unchecked Exceptions :ArrayIndexOutOfBoundException
,ClassCastException
,NullPointerException
further reading : http://javaconceptoftheday.com/difference-between-error-vs-exception-in-java/
answered Sep 15, 2017 at 6:19
roottravellerroottraveller
7,7247 gold badges58 silver badges65 bronze badges
Sun puts it best:
An Error is a subclass of Throwable
that indicates serious problems that a
reasonable application should not try
to catch.
answered May 26, 2009 at 19:48
PowerlordPowerlord
86.5k17 gold badges125 silver badges172 bronze badges
The description of the Error
class is quite clear:
An
Error
is a subclass ofThrowable
that indicates serious problems that a
reasonable application should not try
to catch. Most such errors are
abnormal conditions. TheThreadDeath
error, though a «normal» condition, is
also a subclass ofError
because most
applications should not try to catch
it.A method is not required to declare in
its throws clause any subclasses of
Error
that might be thrown during the
execution of the method but not
caught, since these errors are
abnormal conditions that should never
occur.
Cited from Java’s own documentation of the class Error
.
In short, you should not catch Error
s, except you have a good reason to do so. (For example to prevent your implementation of web server to crash if a servlet runs out of memory or something like that.)
An Exception
, on the other hand, is just a normal exception as in any other modern language. You will find a detailed description in the Java API documentation or any online or offline resource.
answered May 26, 2009 at 19:50
There is several similarities and differences between classes java.lang.Exception
and java.lang.Error
.
Similarities:
-
First — both classes extends
java.lang.Throwable
and as a result
inherits many of the methods which are common to be used when dealing
with errors such as:getMessage
,getStackTrace
,printStackTrace
and
so on. -
Second, as being subclasses of
java.lang.Throwable
they both inherit
following properties:-
Throwable itself and any of its subclasses (including
java.lang.Error
) can be declared in method exceptions list usingthrows
keyword. Such declaration required only forjava.lang.Exception
and subclasses, forjava.lang.Throwable
,java.lang.Error
andjava.lang.RuntimeException
and their subclasses it is optional. -
Only
java.lang.Throwable
and subclasses allowed to be used in thecatch
clause. -
Only
java.lang.Throwable
and subclasses can be used with keyword —throw
.
-
The conclusion from this property is following both java.lang.Error
and java.lang.Exception
can be declared in the method header, can be in catch
clause, can be used with keyword throw
.
Differences:
-
First — conceptual difference:
java.lang.Error
designed to be
thrown by the JVM and indicate serious problems and intended to stop
program execution instead of being caught(but it is possible as for
any otherjava.lang.Throwable
successor).A passage from javadoc description about
java.lang.Error
:…indicates serious problems that a reasonable application should
not try to catch.In opposite
java.lang.Exception
designed to represent errors that
expected and can be handled by a programmer without terminating
program execution.A passage from javadoc description about
java.lang.Exception
:…indicates conditions that a reasonable application might want to
catch. - The second difference between
java.lang.Error
andjava.lang.Exception
that first considered to be a unchecked exception for compile-time exception checking. As the result code throwingjava.lang.Error
or its subclasses don’t require to declare this error in the method header. While throwingjava.lang.Exception
required declaration in the method header.
Throwable and its successor class diagram (properties and methods are omitted).
answered May 4, 2016 at 14:59
IMO an error is something that can cause your application to fail and should not be handled. An exception is something that can cause unpredictable results, but can be recovered from.
Example:
If a program has run out of memory it is an error as the application cannot continue. However, if a program accepts an incorrect input type it is an exception as the program can handle it and redirect to receive the correct input type.
answered May 26, 2009 at 19:50
Mr. WillMr. Will
2,3083 gold badges21 silver badges27 bronze badges
Errors are mainly caused by the environment in which application is running. For example, OutOfMemoryError occurs when JVM runs out of memory or StackOverflowError occurs when stack overflows.
Exceptions are mainly caused by the application itself. For example, NullPointerException occurs when an application tries to access null object or ClassCastException occurs when an application tries to cast incompatible class types.
Source : Difference Between Error Vs Exception In Java
answered Apr 27, 2015 at 14:10
user2485429user2485429
5778 silver badges7 bronze badges
1
Here’s a pretty good summary from Java API what an Error and Exception represents:
An Error is a subclass of Throwable that indicates serious problems that a reasonable application should not try to catch. Most such errors are abnormal conditions. The ThreadDeath error, though a «normal» condition, is also a subclass of Error because most applications should not try to catch it.
A method is not required to declare in
its throws clause any subclasses of
Error that might be thrown during the
execution of the method but not
caught, since these errors are
abnormal conditions that should never
occur.
OTOH, for Exceptions, Java API says:
The class Exception and its subclasses are a form of Throwable that indicates conditions that a reasonable application might want to catch.
answered May 26, 2009 at 19:50
egagaegaga
20.7k10 gold badges45 silver badges60 bronze badges
Errors are caused by the environment where your application or program runs. Most times, you may not recover from it as this ends your application or program. Javadoc advised that you shouldn’t bother catching such errors since the environment e.g. JVM, on such errors is going to quit anyway.
Examples:
VirtualMachineError
— Thrown to indicate that the Java Virtual Machine is broken or has run out of resources necessary for it to continue operating.
OutOfMemoryError
occurs when JVM runs out of memory or
StackOverflowError
occurs when stack runs over.
Exceptions are caused by your application or program itself; maybe due to your own mistake. Most times you can recover from it and your application would still continue to run. You are advised to catch such errors to prevent abnormal termination of your application or program and/or to be able to customize the exception message so the users see a nicely formatted message instead of the default ugly exception messages scattered all over the place.
Examples:
NullPointerException
occurs when an application tries to access null object. or
Trying to access an array with a non-existing index or calling a function with wrong data or parameters.
answered May 19, 2020 at 10:12
pasignaturepasignature
5651 gold badge6 silver badges13 bronze badges
Исключения
- Исключения
- Введение
- Иерархия исключений
- Проверяемые и непроверяемые
- Иерархия
- Классификация
- Error и Exception
- Работа с исключениями
- Обработка исключений
- Правила try/catch/finally
- Расположение catch блоков
- Транзакционность
- Делегирование
- Методы и практики работы с исключительными ситуацими
- Собственные исключения
- Реагирование через re-throw
- Не забывайте указывать причину возникновения исключения
- Сохранение исключения
- Логирование
- Чего нельзя делать при обработке исключений
- Try-with-resources или try-с-ресурсами
- Общие советы
- Избегайте генерации исключений, если их можно избежать простой проверкой
- Предпочитайте
Optional
, если отсутствие значения — не исключительная ситуация - Заранее обдумывайте контракты методов
- Предпочитайте исключения кодам ошибок и
boolean
флагам-признакам успеха
- Обработка исключений
- Исключения и статические блоки
- Многопоточность и исключения
- Проверяемые исключения и их необходимость
- Заключение
- Полезные ссылки
Введение
Начав заниматься программированием, мы, к своему удивлению, обнаружили, что не так уж просто заставить программы делать задуманное. Я могу точно вспомнить момент, когда я понял, что большая часть моей жизни с этих пор будет посвящена поиску ошибок в собственных программах.
(c) Морис Уилкс.
Предположим, вам понадобилась программа, считывающая содержимое файла.
В целом, здесь нет ничего сложного и код, выполняющий поставленную задачу, мог бы выглядеть как-то так:
public List<String> readAll(String path) { BufferedReader br = new BufferedReader(new FileReader(path)); String line; List<String> lines = new ArrayList<>(); while ((line = br.readLine()) != null) { lines.add(line); } return lines; }
И это был бы вполне рабочий вариант, если бы не одно но: мы живём не в идеальном мире. Код, приведённый выше, рассчитан на то, что всё работает идеально: путь до файла указан верный, файл можно прочитать, во время чтения с файлом ничего не происходит, место хранения файла работает без ошибок и еще огромное количество предположений.
Однако, как показывает практика, мир не идеален, а нас повсюду преследуют ошибки и проблемы. Кто-то может указать путь до несуществующего файла, во время чтения может произойти ошибка, например, файл повреждён или удален в процессе чтения и т.д.
Игнорирование подобных ситуаций недопустимо, так как это ведет к нестабильно и непредсказуемо работающему коду.
Значит, на такие ситуации надо реагировать.
Самая простая реакция — это возвращать boolean
— признак успеха или некоторый код ошибки, например, какое-то число.
Пусть, 0 — это код удачного завершения приложения, 1 — это аварийное завершение и т.д.
Мы получаем код возврата и уже на него реагируем.
Подобный ход имеет право на жизнь, однако, он крайне неудобен в повседневной разработке с её тысячами возможных ошибок и проблемных ситуаций.
Во-первых, он слишком немногословен, так как необходимо помнить что означает каждый код возврата, либо постоянно сверяться с таблицей расшифровки, где они описаны.
Во-вторых, такой подход предоставляет не совсем удобный способ обработки возникших ошибок. Более того, нередки ситуации, когда в месте возникновения ошибки непонятно, как реагировать на возникшую проблему. В таком случае было бы удобнее делегировать обработку ошибки вызывающему коду, до места, где будет понятно как реагировать на ошибку.
В-третьих, и это, на мой взгляд, самое главное — это небезопасно, так как подобный способ можно легко проигнорировать.
Lots of newbie’s coming in from the C world complain about exceptions and the fact that they have to put exception handling all over the place—they want to just write their code. But that’s stupid: most C code never checks return codes and so it tends to be very fragile. If you want to build something really robust, you need to pay attention to things that can go wrong, and most folks don’t in the C world because it’s just too damn hard.
One of the design principles behind Java is that I don’t care much about how long it takes to slap together something that kind of works. The real measure is how long it takes to write something solid.In Java you can ignore exceptions, but you have to willfully do it. You can’t accidentally say, «I don’t care.» You have to explicitly say, «I don’t care.»
(c) James Gosling.
Поэтому, в Java
используется другой механизм работы с такими ситуациями: исключения.
Что такое исключение? В некотором смысле можно сказать, что исключение — это некоторое сообщение, уведомляющее о проблеме, незапланированном поведении.
В нашем примере с чтением содержимого файла, источником такого сообщения может являться BufferedReader
или FileReader
. Сообщению необходим получатель/обработчик, чтобы перехватить его и что-то сделать, как-то отреагировать.
Важно понимать, что генерация исключения ломает поток выполнения программы, так как либо это сообщение будет перехвачено и обработано каким-то зарегистрированным получателем, либо программа завершится.
Что значит «ломает поток выполнения программы»?
Представьте, что по дороге едет грузовик. Движение машины и есть поток выполнения программы. Вдруг водитель видит, что впереди разрушенный мост — исключение, ошибка. Теперь он либо поедет по объездной дороге, т.е перехватит и отреагирует на исключение, либо остановится и поездка будет завершена.
Исключения могут быть разных типов, под разные ситуации, а значит и получателей(обработчиков) может быть несколько — на каждый отдельный тип может быть своя реакция, свой обработчик.
Исключение также может хранить информацию о возникшей проблеме: причину, описание-комментарий и т.д.
Исходя из описания можно сказать, что исключение — это объект некоторого, специально для этого предназначенного, класса. Так как проблемы и ошибки бывают разного рода, их можно классифицировать и логически разделить, значит и классы исключений можно выстроить в некоторую иерархию.
Как генерировать исключения и регистрировать обработчики мы рассмотрим позднее, а пока давайте взглянем на иерархию этих классов.
Иерархия исключений
Ниже приведена иерархия исключений:
Картинка большая, чтобы лучше запоминалась.
Для начала разберем загадочные подписи checked
и unchecked
на рисунке.
Проверяемые и непроверяемые
Все исключения в Java
делятся на два типа: проверяемые (checked
) и непроверяемые исключения (unchecked
).
Как видно на рисунке, java.lang.Throwable
и java.lang.Exception
относятся к проверяемым исключениям, в то время как java.lang.RuntimeException
и java.lang.Error
— это непроверяемые исключения.
Принадлежность к тому или иному типу каждое исключение наследует от родителя.
Это значит, что наследники java.lang.RuntimeException
будут unchecked
исключениями, а наследники java.lang.Exception
— checked
.
Что это за разделение?
В первую очередь напомним, что Java
— это компилируемый язык, а значит, помимо runtime
(время выполнения кода), существует ещё и compile-time
(то, что происходит во время компиляции).
Так вот проверяемые исключения — это исключения, на которые разработчик обязан отреагировать, т.е написать обработчики, и наличие этих обработчиков будет проверено на этапе компиляции. Ваш код не будет скомпилирован, если какое-то проверяемое исключение не обработано, компилятор этого не допустит.
Непроверяемые исключения — это исключения времени выполнения. Компилятор не будет от вас требовать обработки непроверяемых исключений.
В чём же смысл этого разделения на проверяемые и непроверяемые исключения?
Я думаю так: проверяемые исключения в Java
— это ситуации, которые разработчик никак не может предотвратить и исключение является одним из вариантов нормальной работы кода.
Например, при чтении файла требуется обрабатывать java.io.FileNotFoundException
и java.io.IOException
, которые является потомками java.io.Exception
.
Потому, что отсутствие файла или ошибка работы с вводом/выводом — это вполне допустимая ситуация при чтении.
С другой стороны, java.lang.RuntimeException
— это скорее ошибки разработчика.
Например, java.lang.NullPointerException
— это ошибка обращения по null
ссылке, данную ситуацию можно предотвратить: проверить ссылку на null
перед вызовом.
Представьте, что вы едете по дороге, так вот предупредительные знаки — это проверяемые исключения. Например, знак «Осторожно, дети!» говорит о том, что рядом школа и дорогу может перебежать ребенок. Вы обязаны отреагировать на это, не обязательно ребенок перебежит вам дорогу, но вы не можете это проконтролировать, но в данном месте — это нормальная ситуация, ведь рядом школа.
Делать абсолютно все исключения проерямыми — не имеет никакого смысла, потому что вы просто с ума сойдете, пока будете писать обработчики таких ситуаций. Да и зачастую это будет только мешать: представьте себе дорогу, которая утыкана постоянными предупредительными знаками, на которые вы должны реагировать. Ехать по такой дороге будет крайне утомительно.
Разделение на проверяемые и непроверяемые исключения существует только в
Java
, в других языках программирования, таких какScala
,Groovy
,Kotlin
илиPython
, все исключения непроверяемые.Это довольно холиварная тема и свои мысли по ней я изложу в конце статьи.
Теперь рассмотрим непосредственно иерархию исключений.
Иерархия
Итак, корнем иерархии является java.lang.Throwable
, у которого два наследника: java.lang.Exception
и java.lang.Error
.
В свою очередь java.lang.Exception
является родительским классом для java.lang.RuntimeException
.
Занятно, что класс
java.lang.Throwable
назван так, как обычно называют интерфейсы, что иногда вводит в заблуждение новичков. Однако помните, что это класс! Запомнить это довольно просто, достаточно держать в уме то, что исключения могут содержать состояние (например, информация о возникшей проблеме).
Так как в Java
все классы являются наследниками java.lang.Object
, то и исключения (будучи тоже классами) наследуют все стандартные методы, такие как equals
, hashCode
, toString
и т.д.
Раз мы работаем с классами, то можно с помощью наследования создавать свои собственные иерархии исключений, добавляя в них какое-то специфическое поведение и состояние.
Чтобы создать свой собственный класс исключение необходимо отнаследоваться от одного из классов в иерархии исключений. При этом помните, что наследуется еще и тип исключения: проверяемое или непроверяемое.
Классификация
Каждый тип исключения отвечает за свою область ошибок.
-
java.lang.Exception
Это ситуации, которые разработчик никак не может предотвратить, например, не получилось закрыть файловый дескриптор или отослать письмо, и исключение является одним из вариантов нормальной работы кода.
Это проверяемые исключения, мы обязаны на такие исключения реагировать, это будет проверено на этапе компиляции.
Пример:
java.io.IOException
,java.io.FileNotFoundException
. -
java.lang.RuntimeException
Это ситуации, когда основной причиной ошибки является сам разработчик, например, происходит обращение к
null
ссылке, деление на ноль, выход за границы массива и т.д. При этом исключение не является одним из вариантов нормальной работы кода.Это непроверяемые исключения, реагировать на них или нет решает разработчик.
Пример:
java.lang.NullPointerException
. -
java.lang.Error
Это критические ошибки, аварийные ситуации, после которых мы с трудом или вообще не в состоянии продолжить работу. Например, закончилась память, переполнился стек вызовов и т.д.
Это непроверяемые исключения, реагировать на них или нет решает разработчик.
Реагировать на подобные ошибки следует только в том случае, если разработчик точно знает как поступить в такой ситуации. Перехватывать такие ошибки не рекомендуется, так как чаще всего разработчик не знает как реагировать на подобного рода аварийные ситуации.
Теперь перейдем к вопросу: в чем же разница между java.lang.Error
и java.lang.Exception
?
Error и Exception
Все просто. Исключения java.lang.Error
— это более серьезная ситуация, нежели java.lang.Exception
.
Это серьезные проблемы в работе приложения, которые тяжело исправить, либо вообще неясно, можно ли это сделать.
Это не просто исключительная ситуация — это ситуация, в которой работоспособность всего приложения под угрозой! Например, исключение java.lang.OutOfMemoryError
, сигнализирующее о том, что кончается память или java.lang.StackOverflowError
– переполнение стека вызовов, которое можно встретить при бесконечной рекурсии.
Согласитесь, что если не получается преобразовать строку к числу, то это не та ситуация, когда все приложение должно завершаться. Это ситуация, после которой приложение может продолжить работать.
Да, это неприятно, что вы не смогли найти файл по указанному пути, но не настолько критично, как переполнение стека вызовов.
Т.е разница — в логическом разделении.
Поэтому, java.lang.Error
и его наследники используются только для критических ситуаций.
Работа с исключениями
Обработка исключений
Корнем иерархии является класс java.lang.Throwable
, т.е. что-то «бросаемое».
А раз исключения бросаются, то для обработки мы будем ловить их!
В Java
исключения ловят и обрабатывают с помощью конструкции try/catch/finally
.
При заключении кода в один или несколько блоков try
указывается потенциальная возможность выбрасывания исключения в этом месте, все операторы, которые могут сгенерировать исключение, помещаются в этом блоке.
В блоках catch
перечисляются исключения, на которые решено реагировать. Тут определяются блоки кода, предназначенные для решения возникших проблем. Это и есть объявление тех самых получателей/обработчиков исключений.
Пример:
public class ExceptionHandling { public static void main(String[] args) { try { // код } catch(FileNotFoundException fnf) { // обработчик на FileNotFoundException } } }
Тот тип исключения, что указывается в catch
блоке можно расценивать как фильтр, который перехватывает все исключения того типа, что вы указали и всех его потомков, расположенных ниже по иерархии.
Представьте себе просеивание муки. Это процесс целью которого является удаление посторонних частиц, отличающихся по размерам от частиц муки. Вы просеиваете через несколько фильтров муку, так как вам не нужны крупные комочки, осколки и другие посторонние частицы, вам нужна именно мука определенного качества. И в зависимости от выставленных фильтров вы будете перехватывать разные частицы, комочки и т.д. Эти частицы и есть исключения. И если выставляется мелкий фильтр, то вы словите как крупные частицы, так и мелкие.
Точно также и в Java
, ставя фильтр на java.lang.RuntimeException
вы ловите не только java.lang.RuntimeException
, но и всех его наследников! Ведь эти потомки — это тоже runtime
ошибки!
В блоке finally
определяется код, который будет всегда выполнен, независимо от результата выполнения блоков try/catch
. Этот блок будет выполняться независимо от того, выполнился или нет блок try
до конца, было ли сгенерировано исключение или нет, и было ли оно обработано в блоке catch
или нет.
Пример:
public class ExceptionHandling { public static void main(String[] args) { try { // some code } catch(FileNotFoundException fnf) { // обработчик 1 } catch(RuntimeException re) { // обработчик 2 } finally { System.out.println("Hello from finally block."); } } }
В примере выше объявлен try
блок с кодом, который потенциально может сгенерировать исключения, после try
блока описаны два обработчика исключений, на случай генерации FileNotFoundException
и на случай генерации любого RuntimeException
.
Объект исключения доступен по ссылке exception
.
Правила try/catch/finally
-
Блок
try
находится перед блокомcatch
илиfinally
. При этом должен присутствовать хотя бы один из этих блоков. -
Между
try
,catch
иfinally
не может быть никаких операторов. -
Один блок
try
может иметь несколькоcatch
блоков. В таком случае будет выполняться первый подходящий блок.Поэтому сначала должны идти более специальные блоки обработки исключений, а потом уже более общие.
-
Блок
finally
будет выполнен всегда, кроме случая, когдаJVM
преждевременно завершит работу или будет сгенерировано исключение непосредственно в самомfinally
блоке. -
Допускается использование вложенных конструкций
try/catch/finally
.public class ExceptionHandling { public static void main(String[] args) { try { try { // some code } catch(FileNotFoundException fnf) { // обработчик 1 } } catch(RuntimeException re) { // обработчик 2 } finally { System.out.println("Hello from finally block."); } } }
Вопрос:
Каков результат выполнения примера выше, если в блоке try
не будет сгенерировано ни одного исключения?
Ответ:
Будет выведено на экран: «Hello from finally block.».
Так как блок finally
выполняется всегда.
Вопрос:
Теперь немного видоизменим код, каков результат выполнения будет теперь?
public class ExceptionHandling { public static void main(String[] args) { try { return; } finally { System.out.println("Hello from finally block"); } } }
Ответ:
На экран будет выведено: Hello from finally block
.
Вопрос:
Плохим тоном считается прямое наследование от java.lang.Throwable
.
Это строго не рекомендуется делать, почему?
Ответ:
Наследование от наиболее общего класса, а в данном случае от корневого класса иерархии, усложняет обработку ваших исключений. Проблему надо стараться локализовать, а не делать ее описание/объявление максимально общим. Согласитесь, что java.lang.IllegalArgumentException
говорит гораздо больше, чем java.lang.RuntimeException
. А значит и реакция на первое исключение будет более точная, чем на второе.
Далее приводится несколько примеров перехвата исключений разных типов:
Обработка java.lang.RuntimeException
:
try { String numberAsString = "one"; Double res = Double.valueOf(numberAsString); } catch (RuntimeException re) { System.err.println("Error while convert string to double!"); }
Результатом будет печать на экран: Error while convert string to double!
.
Обработка java.lang.Error
:
try { throw new Error(); } catch (RuntimeException re) { System.out.println("RuntimeException"); } catch (Error error) { System.out.println("ERROR"); }
Результатом будет печать на экран: ERROR
.
Расположение catch блоков
Как уже было сказано, один блок try
может иметь несколько catch
блоков. В таком случае будет выполняться первый подходящий блок.
Это значит, что порядок расположения catch
блоков важен.
Рассмотрим ситуацию, когда некоторый используемый нами метод может выбросить два разных исключения:
void method() throws Exception { if (new Random((System.currentTimeMillis())).nextBoolean()) { throw new Exception(); } else { throw new IOException(); } }
Конструкция
new Random((System.currentTimeMillis())).nextBoolean()
генерирует нам случайное значениеfalse
илиtrue
.
Для обработки исключений этого метода написан следующий код:
try { method(); } catch (Exception e) { // Обработчик 1 } catch (IOException e) { // Обработчик 2 }
Все ли хорошо с приведенным выше кодом?
Нет, код выше неверен, так как обработчик java.io.IOException
в данном случае недостижим. Все дело в том, что первый обработчик, ответсвенный за Exception
, перехватит все исключения, а значит не может быть ситуации, когда мы сможем попасть во второй обработчик.
Снова вспомним пример с мукой, приведенный в начале.
Так вот песчинка, которую мы ищем, это и есть наше исключение, а каждый фильтр это catch
блок.
Если первым установлен фильтр ловить все, что является Exception и его потомков, то до фильтра ловить все, что является IOException и его потомков ничего не дойдет, так как верхний фильтр уже перехватит все песчинки.
Отсюда следует правило:
Сначала должны идти более специальные блоки обработки исключений, а потом уже более общие.
А что если на два разных исключения предусмотрена одна и та же реакция? Написание двух одинаковых catch
блоков не приветствуется, ведь дублирование кода — это зло.
Поэтому допускается объединить два catch
блока с помощью |
:
try { method2(); } catch (IllegalArgumentException | IndexOutOfBoundsException e) { // Обработчик }
Вопрос:
Есть ли способ перехватить все возможные исключения?
Ответ:
Есть! Если взглянуть еще раз на иерархию, то можно отметить, что java.lang.Throwable
является родительским классом для всех исключений, а значит, чтобы поймать все, необходимо написать что-то в виде:
try { method(); } catch (Throwable t) { // Обработчик }
Однако, делать так не рекомендуется, что наталкивает на следующий вопрос.
Вопрос:
Почему перехватывать java.lang.Throwable
— плохо?
Ответ:
Дело в том, что написав:
try { method(); } catch (Throwable t) { // catch all }
Будут перехвачены абсолютно все исключения: и java.lang.Exception
, и java.lang.RuntimeException
, и java.lang.Error
, и все их потомки.
И как реагировать на все? При этом надо учесть, что обычно на java.lang.Error
исключений вообще не ясно как реагировать. А значит, мы можем неверно отреагировать на исключение и вообще потерять данные. А ловить то, что не можешь и не собирался обрабатывать — плохо.
Поэтому перехватывать все исключения — плохая практика.
Вопрос-Тест:
Что будет выведено на экран при запуске данного куска кода?
public static void main(String[] args) { try { try { throw new Exception("0"); } finally { if (true) { throw new IOException("1"); } System.err.println("2"); } } catch (IOException ex) { System.err.println(ex.getMessage()); } catch (Exception ex) { System.err.println("3"); System.err.println(ex.getMessage()); } }
Ответ:
При выполнении данного кода выведется «1».
Давайте разберем почему.
Мы кидаем исключение во вложенном try
блоке: throw new Exception("0");
.
После этого поток программы ломается и мы попадаем в finally
блок:
if (true) { throw new IOException("1"); } System.err.println("2");
Здесь мы гарантированно зайдем в if
и кинем уже новое исключение: throw new IOException("1");
.
При этом вся информация о первом исключении будет потеряна! Ведь мы никак не отреагировали на него, а в finally
блоке и вовсе ‘перезатерли’ новым исключением.
На try
, оборачивающий наш код, настроено два фильтра: первый на IOException
, второй на Exception
.
Так как порядок расположения задан так, что мы прежде всего смотрим на IOException
, то и сработает этот фильтр, который выполнит следующий код:
System.err.println(ex.getMessage());
Именно поэтому выведется 1
.
Транзакционность
Важным моментом, который нельзя пропустить, является то, что try
блок не транзакционный.
Под термином
транзакционность
я имею в виду то, что либо действия будут выполнены целиком и успешно, либо не будут выполнены вовсе.
Что это значит?
Это значит, что при возникновении исключения в try
блоке все совершенные действия не откатываются к изначальному состоянию, а так и остаются совершенными.
Все выделенные ресурсы так и остаются занятыми, в том числе и при возникновении исключения.
По сути именно поэтому и существует finally
блок, так как туда, как уже было сказано выше, мы зайдем в любом случае, то там и освобождают выделенные ресурсы.
Вопрос:
Работа с объектами из try
блока в других блоках невозможна:
public class ExceptionExample { public static void main(String[] args) { try { String line = "hello"; } catch (Exception e) { System.err.println(e); } // Compile error System.out.println(line); // Cannot resolve symbol `line` } }
Почему?
Ответ:
Потому что компилятор не может нам гарантировать, что объекты, объявленные в try
-блоке, были созданы.
Ведь могло быть сгенерировано исключение. Тогда после места, где было сгенерировано исключение, оставшиеся действия не будут выполнены, а значит возможна ситуация, когда объект не будет создан. Следовательно и работать с ним нельзя.
Вернемся к примеру с грузовиком, чтобы объяснить все вышесказанное.
Объездная здесь — это catch
блок, реакция на исключительную ситуацию. Если добавить еще несколько объездных дорог, несколько catch
блоков, то водитель выберет наиболее подходящий путь, наиболее подходящий и удобный catch
блок, что объясняет важность расположения этих блоков.
Транзакционность на этом примере объясняется тем, что если до этого водитель где-то оплатил проезд по мосту, то деньги ему автоматически не вернутся, необходимо будет написать в поддержку или куда-то пожаловаться на управляющую компанию.
Делегирование
Выше было разобрано то, как обрабатывать исключения. Однако, иногда возникают ситуации, когда в нет конкретного понимания того, как обрабатывать возникшее исключение. В таком случае имеет смысл делегировать задачу обработки исключения коду, который вызвал ваш метод, так как вызывающий код чаще всего обладает более обширными сведениями об источнике проблемы или об операции, которая сейчас выполняется.
Делегирование исключения производится с помощью ключевого слова throws
, которое добавляется после сигнатуры метода.
Пример:
// Код написан только для ознакомительной цели, не стоит с него брать пример! String readLine(String path) throws IOException { BufferedReader br = new BufferedReader(...); String line = br.readLine(); return line; }
Таким образом обеспечивается передача объявленного исключения в место вызова метода. И то, как на него реагировать уже становится заботой вызывающего этот метод.
Поэтому реагировать и писать обработчики на те исключения, которые мы делегировали, внутри метода уже не надо.
Механизм throws
введен для проброса проверяемых исключений.
Разумеется, с помощью throws
можно описывать делегирование как проверяемых, так и непроверяемых исключений.
Однако перечислять непроверяемые не стоит, такие исключения не контролируются в compile time
.
Перечисление непроверяемых исключений бессмысленно, так как это примерно то же самое, что перечислять все, что может с вами случиться на улице.
Теперь пришла пора рассмотреть методы обработки исключительных ситуаций.
Методы и практики работы с исключительными ситуацими
Главное и основное правило при работе с исключениями звучит так:
На исключения надо либо реагировать, либо делегировать, но ни в коем случае не игнорировать.
Определить когда надо реагировать, а когда делегировать проще простого. Задайте вопрос: «Знаю ли я как реагировать на это исключение?».
Если ответ «да, знаю», то реагируйте, пишите обработчик и код, отвечающий за эту реакцию, если не знаете что делать с исключением, то делегируйте вызывающему коду.
Собственные исключения
Выше мы уже затронули то, что исключения это те же классы и объекты.
И иногда удобно выстроить свою иерархию исключений, заточенных под конкретную задачу. Дабы более гибко обрабатывать и реагировать на те исключительные ситуации, которые специфичны решаемой задаче.
Например, пусть есть некоторый справочник:
class Catalog { Person findPerson(String name); }
В данном случае нам надо обработать ситуации, когда name
является null
, когда в каталоге нет пользователя с таким именем.
Если генерировать на все ситуации java.lang.Exception
, то обработка ошибок будет крайне неудобной.
Более того, хотелось бы явно выделить ошибку, связанную с тем, что пользователя такого не существует.
Очевидно, что стандартное исключение для этого случая не существует, а значит вполне логично создать свое.
class PersonNotFoundException extends RuntimeException { private String name; // some code }
Обратите внимание, что имя Person
, по которому в каталоге не смогли его найти, выделено в свойство класса name
.
Теперь при использовании этого метода проще реагировать на различные ситуации, такие как null
вместо имени, а проблему с отсутствием Person
в каталоге можно отдельно вынести в свой catch
блок.
Реагирование через re-throw
Часто бывает необходимо перехватить исключение, сделать запись о том, что случилось (в файл лога, например) и делегировать его вызывающему коду.
Как уже было сказано выше, в рамках конструкции try/catch/finally
можно сгенерировать другое исключение.
Такой подход называется re-throw
.
Исключение перехватывается в catch
блоке, совершаются необходимые действия, например, запись в лог или создание нового, более конкретного для контекста задачи, исключения и повторная генерация исключения.
Как это выглядит на практике:
try { Reader readerConf = .... readerConf.readConfig(); } catch(IOException ex) { System.err.println("Log exception: " + ex); throw new ConfigException(ex); }
Во время чтения конфигурационного файла произошло исключение java.io.IOException
, в catch
блоке оно было перехвачено, сделана запись в консоль о проблеме, после чего было создано новое, более конкретное, исключение ConfigException
, с указанием причины (перехваченное исключение, ссылка на которое ex
) и оно было проброшено дальше.
По итогу, из метода с приведенным кодом, в случае ошибки чтения конфигурации, будет выброшено ConfigException
.
Для чего мы здесь так поступили?
Это полезно для более гибкой обработки исключений.
В примере выше чтение конфигурации генерирует слишком общее исключение, так как java.io.IOException
это довольно общее исключение, но проблема в примере выше понятна: работа с этим конфигурационным файлом невозможна.
Значит и сообщить лучше именно как о том, что это не абстрактный java.io.IOException
, а именно ConfigException
. При этом, так как перехваченное исключение было передано новому в конструкторе, т.е. указалась причина возникновения (cause) ConfigException
, то при выводе на консоль или обработке в вызывающем коде будет понятно почему ConfigException
был создан.
Также, можно было добавить еще и текстовое описание к сгенерированному ConfigException
, более подробно описывающее произошедшую ситуацию.
Еще одной важной областью применения re-throw
бывает преобразование проверяемых исключений в непроверяемые.
В Java 8
даже добавили исключение java.io.UncheckedIOException
, которое предназначено как раз для того, чтобы сделать java.io.IOException
непроверяемым, обернуть в unchecked
обертку.
Пример:
try { Reader readerConf = .... readerConf.readConfig(); } catch(IOException ex) { System.err.println("Log exception: " + ex); throw new UncheckedIOException(ex); }
Не забывайте указывать причину возникновения исключения
В предыдущем пункте мы создали собственное исключение, которому указали причину: перехваченное исключение, java.io.IOException
.
Чтобы понять как это работает, давайте рассмотрим наиболее важные поля класса java.lang.Throwable
:
public class Throwable implements Serializable { /** * Specific details about the Throwable. For example, for * {@code FileNotFoundException}, this contains the name of * the file that could not be found. * * @serial */ private String detailMessage; // ... /** * The throwable that caused this throwable to get thrown, or null if this * throwable was not caused by another throwable, or if the causative * throwable is unknown. If this field is equal to this throwable itself, * it indicates that the cause of this throwable has not yet been * initialized. * * @serial * @since 1.4 */ private Throwable cause = this; // ... }
Все исключения, будь то java.lang.RuntimeException
, либо java.lang.Exception
имеют необходимые конструкторы для инициализации этих полей.
При создании собственного исключения не пренебрегайте этими конструкторами!
Поле cause
используются для указания родительского исключения, причины. Например, выше мы перехватили java.io.IOException
, прокинув свое исключение вместо него. Но причиной того, что наш код выкинул ConfigException
было именно исключение java.io.IOException
. И эту причину нельзя игнорировать.
Представьте, что код, использующий ваш метод также перехватил ConfigException
, пробросив какое-то своё исключение, а это исключение снова кто-то перехватил и пробросил свое. Получается, что истинная причина будет просто потеряна! Однако, если каждый будет указывать cause
, истинного виновника возникновения исключения, то вы всегда сможете обнаружить по этому стеку виновника.
Для получения причины возникновения исключения существует метод getCause.
public class ExceptionExample { public Config readConfig() throws ConfigException { // (1) try { Reader readerConf = ....; readerConf.readConfig(); } catch (IOException ex) { System.err.println("Log exception: " + ex); throw new ConfigException(ex); // (2) } } public void run() { try { Config config = readConfig(); // (3) } catch (ConfigException e) { Throwable t = e.getCause(); // (4) } } }
В коде выше:
- В строке (1) объявлен метод
readConfig
, который может выброситьConfigException
. - В строке (2) создаётся исключение
ConfigException
, в конструктор которого передаетсяIOException
— причина возникновения. readConfig
вызывается в (3) строке кода.- А в (4) вызван метод
getCause
который и вернёт причину возникновенияConfigException
—IOException
.
Сохранение исключения
Исключения необязательно генерировать, пробрасывать и так далее.
Выше уже упоминалось, что исключение — это Java
-объект. А значит, его вполне можно присвоить переменной или свойству класса, передать по ссылке в метод и т.д.
class Reader { // A holder of the last IOException encountered private IOException lastException; // some code public void read() { try { Reader readerConf = .... readerConf.readConfig(); } catch(IOException ex) { System.err.println("Log exception: " + ex); lastException = ex; } } }
Генерация исключения это довольно дорогостоящая операция. Кроме того, исключения ломают поток выполнения программы. Чтобы не ломать поток выполнения, но при этом иметь возможность в дальнейшем отреагировать на исключительную ситуацию можно присвоить ее свойству класса или переменой.
Подобный прием использован в java.util.Scanner
, где генерируемое исключение чтения потока сохраняется в свойство класса lastException
.
Еще одним способом применения сохранения исключения может являться ситуация, когда надо сделать N операций, какие-то из них могут быть не выполнены и будет сгенерировано исключение, но реагировать на эти исключения будут позже, скопом.
Например, идет запись в базу данных тысячу строк построчно.
Из них 100 записей происходит с ошибкой.
Эти исключения складываются в список, а после этот список передается специальному методу, который по каждой ситуации из списка как-то отреагирует.
Т.е пока делаете операцию, копите ошибки, а потом уже реагируете.
Это похоже на то, как опрашивают 1000 человек, а негативные отзывы/голоса записывают, после чего реагируют на них. Согласитесь, было бы глупо после каждого негативного отзыва осуществлять реакцию, а потом снова возвращаться к толпе и продолжать опрос.
class Example { private List<Exception> exceptions; // some code public void parse(String s) { try { // do smth } catch(Exception ex) { exceptions.add(ex); } } private void handleExceptions() { for(Exception e : exceptions) { System.err.println("Log exception: " + e); } } }
Логирование
Когда логировать исключение?
В большинстве случаев лучше всего логировать исключение в месте его обработки. Это связано с тем, что именно в данном месте кода достаточно информации для описания возникшей проблемы — реакции на исключение. Кроме этого, одно и то же исключение при вызове одного и того же метода можно перехватывать в разных местах программы.
Также, исключение может быть частью ожидаемого поведения. В этом случае нет необходимости его логировать.
Поэтому не стоит преждевременно логировать исключение, например:
/** * Parse date from string to java.util.Date. * @param date as string * @return Date object. */ public static Date from(String date) { try { DateFormat format = new SimpleDateFormat("MMMM d, yyyy", Locale.ENGLISH); return format.parse(date); } catch (ParseException e) { logger.error("Can't parse ") throw e; } }
Здесь ParseException
является частью ожидаемой работы, в ситуациях, когда строка содержит невалидные данные.
Раз происходит делегирование исключения выше (с помощью throw
), то и там, где его будут обрабатывать и лучше всего логировать, а эта запись в лог будет избыточной. Хотя бы потому, что в месте обработки исключения его тоже залогируют!
Подробнее о логировании.
Чего нельзя делать при обработке исключений
-
Старайтесь не игнорировать исключения.
В частности, никогда не пишите подобный код:
try { Reader readerConf = .... readerConf.readConfig(); } catch(IOException e) { e.printStackTrace(); }
-
Не следует писать ‘универсальные’ блоки обработки исключений.
Ведь очень трудно представить себе метод, который одинаково реагировал бы на все возникающие проблемы.
Также программный код может измениться, а ‘универсальный’ обработчик исключений будет продолжать обрабатывать новые типы исключений одинаково.
Поэтому таких ситуаций лучше не допускать.
-
Старайтесь не преобразовывать более конкретные исключения в более общие.
В частности, например, не следует
java.io.IOException
преобразовывать вjava.lang.Exception
или вjava.lang.Throwable
.Чем с более конкретными исключениями идет работа, тем проще реагировать и принимать решения об их обработке.
-
Старайтесь не злоупотреблять исключениями.
Если исключение можно не допустить, например, дополнительной проверкой, то лучше так и сделать.
Например, можно обезопасить себя от
java.lang.NullPointerException
простой проверкой:if(ref != null) { // some code }
Try-with-resources или try-с-ресурсами
Как уже говорилось выше про finally
блок, код в нем выполняется в любом случае, что делает его отличным кандидатом на место по освобождению ресурсов, учитывая нетранзакционность блока try
.
Чаще всего за закрытие ресурса будет отвечать код, наподобие этого:
try { // code } finally { resource.close(); }
Освобождение ресурса (например, освобождение файлового дескриптора) — это поведение.
А за поведение в
Java
отвечают интерфейсы.
Это наталкивает на мысль, что нужен некоторый общий интерфейс, который бы реализовывали все классы, для которых необходимо выполнить какой-то код по освобождению ресурсов, т.е выполнить ‘закрытие’ в finally
блоке и еще удобнее, если бы этот однообразный finally
блок не нужно было писать каждый раз.
Поэтому, начиная с Java 7
, была введена конструкция try-with-resources
или TWR
.
Для этого объявили специальный интерфейс java.lang.AutoCloseable
, у которого один метод:
void close() throws Exception;
Все классы, которые будут использоваться так, как было описано выше, должны реализовать или java.lang.Closable
, или java.lang.AutoCloseable
.
В качестве примера, напишем код чтения содержимого файла и представим две реализации этой задачи: используя и не используя try-with-resources
.
Без использования try-with-resources
(пример ниже плох и служит только для демонстрации объема необходимого кода):
BufferedReader br = null; try { br = new BufferedReader(new FileReader(path)); // read from file } catch (IOException e) { // catch and do smth } finally { try { if (br != null) { br.close(); } } catch (IOException ex) { // catch and do smth } }
А теперь то же самое, но в Java 7+
:
try (FileReader fr = new FileReader(path); BufferedReader br = new BufferedReader(fr)) { // read from file } catch (IOException e) { // catch and do smth }
По возможности пользуйтесь только try-with-resources
.
Помните, что без реализации
java.lang.Closable
илиjava.lang.AutoCloseable
ваш класс не будет работать сtry-with-resources
так, как показано выше.
Вопрос:
Получается, что используя TWR
мы не пишем код для закрытия ресурсов, но при их закрытии может же тоже быть исключение! Что произойдет?
Ответ:
Точно так же, как и без TWR
, исключение выбросится так, будто оно было в finally
-блоке.
Помните, что TWR
, грубо говоря, просто добавляет вам блок кода вида:
finally { resource.close(); }
Вопрос:
Является ли безопасной конструкция следующего вида?
try (BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(new FileOutputStream("a")))) { }
Ответ:
Не совсем, если конструктор OutputStreamWriter
или BufferedWriter
выбросит исключение, то FileOutputStream
закрыт не будет.
Пример, демонстрирующий это:
public class Main { public static void main(String[] args) throws Exception { try (ThrowingAutoCloseable throwingAutoCloseable = new ThrowingAutoCloseable(new PrintingAutoCloseable())) { // (1) } } private static class ThrowingAutoCloseable implements AutoCloseable { // (2) private final AutoCloseable other; public ThrowingAutoCloseable(AutoCloseable other) { this.other = other; throw new IllegalStateException("I always throw"); // (3) } @Override public void close() throws Exception { try { other.close(); // (4) } finally { System.out.println("ThrowingAutoCloseable is closed"); } } } private static class PrintingAutoCloseable implements AutoCloseable { // (5) public PrintingAutoCloseable() { System.out.println("PrintingAutoCloseable created"); // (6) } @Override public void close() { System.out.println("PrintingAutoCloseable is closed"); // (7) } } }
- В строке (1) происходит заворачивание одного ресурса в другой, аналогично
new BufferedWriter(new OutputStreamWriter(new FileOutputStream("a")))
. ThrowingAutoCloseable
(2) — такойAutoCloseable
, который всегда бросает исключение (3), в (4) производится попытка закрыть полученный в конструктореAutoCloseable
.PrintingAutoCloseable
(5) —AutoCloseable
, который печатает сообщения о своём создании (6) и закрытии (7).
В результате выполнения этой программы вывод будет примерно следующим:
PrintingAutoCloseable created
Exception in thread "main" java.lang.IllegalStateException: I always throw
at ru.misc.Main$ThrowingAutoCloseable.<init>(Main.java:19)
at ru.misc.Main.main(Main.java:9)
Как видно, PrintingAutoCloseable
закрыт не был!
Вопрос:
В каком порядке закрываются ресурсы, объявленные в try-with-resources?
Ответ:
В обратном.
Пример:
public class Main { public static void main(String[] args) throws Exception { try (PrintingAutoCloseable printingAutoCloseable1 = new PrintingAutoCloseable("1"); PrintingAutoCloseable printingAutoCloseable2 = new PrintingAutoCloseable("2"); PrintingAutoCloseable printingAutoCloseable3 = new PrintingAutoCloseable("3")) { } } private static class PrintingAutoCloseable implements AutoCloseable { private final String id; public PrintingAutoCloseable(String id) { this.id = id; } @Override public void close() { System.out.println("Closed " + id); } } }
Вывод:
Closed 3
Closed 2
Closed 1
Общие советы
Избегайте генерации исключений, если их можно избежать простой проверкой
Как уже было сказано выше, исключения ломают поток выполнения программы. Если же на сгенерированное исключение не найдется обработчика, не будет подходящего catch
блока, то программа и вовсе будет завершена. Кроме того, генерация исключения это довольно дорогостоящая операция.
Помните, что если исключение можно не допустить, то лучше так и сделать.
Отсюда следует первый совет: не брезгуйте дополнительными проверками.
- Не ловите
IllegalArgumentException
,NullPointerException
,ArrayIndexOutOfBoundsException
и подобные.
Потому что эти ошибки — это явная отсылка к тому, что где-то недостает проверки.
Обращение по индексу за пределами массива,NullPointerException
, все эти исключения — это ошибка разработчика. - Вводите дополнительные проверки на данные, дабы избежать возникновения непроверяемых исключения
Например, запретите вводить в поле возраста не числовые значения, проверяйте ссылки на null
перед обращением и т.д.
Предпочитайте Optional
, если отсутствие значения — не исключительная ситуация
При написании API
к каким-то хранилищам или коллекциям очень часто на отсутствие элемента генерируется исключение, как например в разделе собственные исключения.
class Catalog { Person findPerson(String name); }
Но и в этом случае генерации исключения можно избежать, если воспользоваться java.util.Optional
:
Optional<Person> findPerson(String name);
Класс java.util.Optional
был добавлен в Java 8
и предназначен как раз для подобных ситуаций, когда возвращаемого значения может не быть. В зависимости от задачи и контекста можно как генерировать исключение, как это сделано в примере с PersonNotFoundException
, так и изменить сигнатуру метода, воспользовавшись java.util.Optional
.
Отсюда следует второй совет: думайте над API
ваших классов, исключений можно избежать воспользовавшись другим подходом.
Заранее обдумывайте контракты методов
Важным моментом, который нельзя не упомянуть, является то, что если в методе объявляется, что он может сгенерировать исключение (с помощью throws
), то при переопределении такого метода нельзя указать более общее исключение в качестве выбрасываемого.
class Person { void hello() throws RuntimeException { // some code } } // Compile Error class PPerson extends Person { @Override void hello() throws Exception { // some code } }
Если было явно указано, что метод может сгенерировать java.lang.RuntimeException
, то нельзя объявить более общее бросаемое исключение при переопределении. Но можно указать потомка:
// IllegalArgumentException - потомок RuntimeException! class PPerson extends Person { @Override void hello() throws IllegalArgumentException { // some code } }
Что, в целом логично.
Если объявляется, что метод может сгенерировать java.lang.RuntimeException
, а он выбрасывает java.io.IOException
, то это было бы как минимум странно.
Это объясняется и с помощью полимофризма. Пусть есть интерфейс, в котором объявлен метод, генерирующий исключение. Если полиморфно работать с объектом через общий интерфейс, то разработчик обязан обработать исключение, объявленное в интерфейсе, а если одна из реализаций интерфейса генерирует более общее исключение, то это нарушает полиморфизм. Поэтому такой код даже не скомпилируется.
При этом при переопределении можно вообще не объявлять бросаемые исключения, таким образом сообщив, что все проблемы будут решены в методе:
class PPerson extends Person { @Override void hello() { // some code } }
Отсюда следует третий совет: необходимо думать о тех исключениях, которые делегирует метод, если класс может участвовать в наследовании.
Предпочитайте исключения кодам ошибок и boolean
флагам-признакам успеха
- Исключения более информативны: они позволяют передать сообщение с описанием ошибки
- Исключение практически невозможно проигнорировать
- Исключение может быть обработано кодом, находящимся выше по стеку, а
boolean
-флаг или код ошибки необходимо обрабатывать здесь и сейчас
Исключения и статические блоки
Еще интересно поговорить про то, что происходит, если исключение возникает в статическом блоке.
Так вот, такие исключения оборачиваются в java.lang.ExceptionInInitializerError
:
public class ExceptionHandling { static { throwRuntimeException(); } private static void throwRuntimeException() { throw new NullPointerException(); } public static void main(String[] args) { System.out.println("Hello World"); } }
Результатом будет падение со следующим стектрейсом:
java.lang.ExceptionInInitializerError Caused by: java.lang.NullPointerException at exception.test.ExceptionHandling.throwRuntimeException(ExceptionHandling.java:13) at exception.test.ExceptionHandling. (ExceptionHandling.java:8)
Многопоточность и исключения
Код в Java
потоке выполняется в методе со следующей сигнатурой:
Что делает невозможным пробрасывание проверяемых исключений, т.е разработчик должен обрабатывать все проверяемые исключения внутри метода run
.
Непроверяемые исключения обрабатывать необязательно, однако необработанное исключение, выброшенное из run
, завершит работу потока.
Например:
public class ExceptionHandling4 { public static void main(String[] args) throws InterruptedException { Thread t = new Thread() { @Override public void run() { throw new RuntimeException("Testing unhandled exception processing."); } }; t.start(); } }
Результатом выполнения этого кода будет то, что возникшее исключение прервет поток исполнения (interrupt thread):
Exception in thread “Thread-0” java.lang.RuntimeException: Testing unhandled exception processing. at exception.test. ExceptionHandling4$1.run(ExceptionHandling4.java:27)
При использовании нескольких потоков бывают ситуации, когда надо знать, как поток завершился, из-за какого именно исключения. И, разумеется, отреагировать на это.
В таких ситуациях рекомендуется использовать Thread.UncaughtExceptionHandler
.
t.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() { public void uncaughtException(Thread t, Throwable e) { System.out.println("Handled uncaught exception in thread :" + t + " Exception : " + e); } });
И вывод уже будет:
Handled uncaught exception in thread :Thread[Thread-0,5,main] Exception : java.lang.RuntimeException: Testing unhandled exception processing.
Необработанное исключение RuntimeException("Testing unhandled exception processing.")
, убившее поток, было перехвачено специальным зарегистрированным обработчиком.
Проверяемые исключения и их необходимость
В большинстве языков программирования, таких как C#
, Scala
, Groovy
, Python
и т.д., нет такого разделения, как в Java
, на проверяемые и непроверяемые исключения.
Почему оно введено в Java
было разобрано выше, а вот почему проверяемые исключения недолюбливают разработчики?
Основных причин две, это причины с: версионированием и масштабируемостью.
Представим, что вы, как разработчик библиотеки, объявили некоторый условный метод foo
, бросающий исключения A
, B
и C
:
void foo() throws A, B, C;
В следующей версии библиотеки в метод foo
добавили функциональности и теперь он бросает еще новое исключение D
:
void foo() throws A, B, C, D;
В таком случае новая версия библиотеки сломает код тех, кто ей пользуется. Это сравнимо с тем, что добавляется новый метод в интерфейс.
И с одной стороны, это правильно, так как в новой версии добавляется еще одно исключение и те, кто использует библиотеку должны отреагировать на все новые исключения. С другой стороны, чаще всего такие исключения будут также проброшены дальше. Все дело в том, что случаев, когда можно обработать специфический тип исключения, например тот же D
или A
в примере выше, и сделать в обработчике что-то интеллектуальное, можно пересчитать по пальцам одной руки.
Проблема с масштабируемостью начинается тогда, когда происходит вызов не одного, а нескольких API
, каждый из которых также несет с собой проверяемые исключения. Представьте, что помимо foo
, бросающего A
, B
, C
и D
, в методе hello
вызывается еще и bar
, который также бросает E
и T
исключения. Как сказано выше, как реагировать чаще всего непонятно, поэтому эти исключения делегируются вызывающему коду, из-за чего объявление метода hello
выглядит совсем уж угрожающе:
void hello() throws A, B, C, D, E, T { try { foo(); bar(); } finally { // clear resources if needed } }
Все это настолько раздражающе, что чаще всего разработчики просто объявляют наиболее общее исключение в throws
:
void hello() throws Exception { try { foo(); bar(); } finally { // clear resources if needed } }
А в таком случае это все равно, что сказать «метод может выбросить исключение» — это настолько общие и абстрактные слова, что смысла в throws Exception
практически нет.
Также есть еще одна проблема с проверяемыми исключениями. Это то, что с проверяемыми исключениями крайне неудобно работать в lambda
-ах и stream
-ах:
// compilation error Lists.newArrayList("a", "asg").stream().map(e -> {throw new Exception();});
Так как с Java 8
использование lambda
и stream
-ов распространенная практика, то накладываемые ограничения вызовут дополнительные трудности при использовании проверяемых исключений.
Поэтому многие разработчики недолюбливают проверяемые исключения, например, оборачивая их в непроверяемые аналоги с помощью re-throw
.
Мое мнение таково: на проверяемых исключениях очень хорошо учиться. Компилятор и язык сами подсказывают вам, что нельзя игнорировать исключения и требуют от вас реакции. Опять же, логическое разделение на проверяемые и непроверяемые помогает в понимании исключений, в понимании того, как и на что реагировать. В промышленной же разработке это становится уже больше раздражающим фактором.
В своей работе я стараюсь чаще использовать непроверяемые исключения, а проверяемые оборачивать в unchecked
аналоги, как, например, java.io.IOException
и java.io.UncheckedIOException
.
Заключение
Иерархия исключений в Java
.
Исключения делятся на два типа: непроверяемые(unchecked
) и проверяемые(checked
). Проверяемые исключения — это исключения, которые проверяются на этапе компиляции, мы обязаны на них отреагировать.
Проверяемые исключения в Java
используются тогда, когда разработчик никак не может предотвратить их возникновение. Причину возникновения java.lang.RuntimeException
можно проверить и устранить заранее, например, проверить ссылку на null
перед вызовом метода, на объекте по ссылке. А вот с причинами проверяемых исключений так сделать не получится, так как ошибка при чтении файла может возникнуть непосредственно в момент чтения, потому что другая программа его удалила. Соответственно, при чтении файла требуется обрабатывать java.io.IOException
, который является потомком java.lang.Exception
.
Допускается создание собственных исключений, признак проверяемости или непроверяемости наследуется от родителя. Исключения — это такие же классы, со своим поведением и состоянием, поэтому при наследовании вполне допускается добавить дополнительное поведение или свойства классу.
Обработка исключений происходит с помощью конструкции try/catch/finally
. Один блок try
может иметь несколько catch
блоков. В таком случае будет выполняться первый подходящий блок.
Помните, что try
блок не транзакционен, все ресурсы, занятые в try
ДО исключения остаются в памяти. Их надо освобождать и очищать вручную.
Если вы используете Java
версии 7 и выше, то отдавайте предпочтение конструкции try-with-resources
.
Основное правило:
На исключения можно реагировать, их обработку можно делегировать, но ни в коем случае нельзя их игнорировать.
Определить когда надо реагировать, а когда делегировать проще простого. Задайте вопрос: «Знаю ли я как реагировать на это исключение?».
Если ответ «да, знаю», то реагируйте, пишите обработчик и код, отвечающий за эту реакцию, если не знаете что делать с исключением, то делегируйте вызывающему коду.
Помните, что перехват java.lang.Error
стоит делать только если вы точно знаете, что делаете. Восстановление после таких ошибок не всегда возможно и почти всегда нетривиально.
Не забывайте, что большинство ошибок java.lang.RuntimeException
и его потомков можно избежать.
Не бойтесь создавать собственные исключения, так как это позволит писать более гибкие обработчики, а значит более точно реагировать на проблемы.
Представьте себе, что существуют пять причин, по которым может быть выброшено исключение, и во всех пяти случаях бросается
java.lang.Exception
. Вы же спятите разбираться, чем именно это исключение вызвано.(c) Евгений Матюшкин.
Помните, что исключения ломают поток выполнения программы, поэтому чем раньше вы обработаете возникшую проблему, тем лучше. Отсюда же следует совет, что лучше не разбрасываться исключениями, так как помимо того, что это ломает поток выполнения, это еще и дорогостоящая операция.
Постарайтесь не создавать ‘универсальных’ обработчиков, так как это чревато трудноуловимыми ошибками.
Если исключение можно не генерировать, то лучше так и сделать. Не пренебрегайте проверками.
Старайтесь продумывать то, как вы будете реагировать на исключения, не игнорировать их, использовать только try-с-ресурсами
.
Помните:
In Java you can ignore exceptions, but you have to willfully do it. You can’t accidentally say, «I don’t care.» You have to explicitly say, «I don’t care.»
(c) James Gosling.
Для закрепления материала рекомендую ознакомиться с ссылками ниже и этим материалом.
Полезные ссылки
- Книга С. Стелтинг ‘Java без сбоев: обработка исключений, тестирование, отладка’
- Oracle Java Tutorials
- Лекция Технострим Исключения
- Лекция OTUS Исключения в Java
- Лекция Ивана Пономарёва по исключениям
- Заметка Евгения Матюшкина про Исключения
- Failure and Exceptions by James Gosling
- The Trouble with Checked Exceptions by Bill Venners with Bruce Eckel
- Никто не умеет обрабатывать ошибки
- Исключения и обобщенные типы в Java
- Вопросы для закрепления
1. Overview
In this tutorial, we’ll learn about Java errors and exceptions and their differences.
2. The Throwable Class
Error and Exception are both subclasses of the Throwable class and are used to indicate that an abnormal situation has happened. Furthermore, only instances of Throwable and its subclasses can be thrown by the Java Virtual Machine or caught in a catch clause.
Instances of Error and Exception are created to include information about the situation (for example, the stack trace):
3. Error
Errors indicate abnormal situations that should never happen. An error is thrown when a serious problem has occurred. Further, errors are regarded as unchecked exceptions, and applications should not try to catch and handle them. Moreover, errors happen at run time and cannot be recovered.
Now let’s see an example:
public class ErrorExample {
public static void main(String[] args) {
throw new AssertionError();
}
}
If we run the above code, we get the following:
Exception in thread "main" java.lang.AssertionError:
at com.baeldung.exception.exceptions_vs_errors.ErrorExample.main(ErrorExample.java:6)
The code caused an error called the AssertionError, which is thrown to indicate when an assertion has failed.
Other examples of Java errors include StackOverflowError, LinkageError, IOError, and VirtualMachineError.
4. Exception
Exceptions are abnormal conditions that applications might want to catch and handle. Exceptions can be recovered using a try-catch block and can happen at both run time and compile time.
Some of the techniques used for exception handling are try-catch block, throws keyword, and try-with-resources block.
Exceptions are divided into two categories: Runtime Exceptions and Checked Exceptions.
4.1. Runtime Exceptions
RuntimeException and its subclasses are the exceptions that can be thrown while the Java Virtual Machine is running. Further, they are unchecked exceptions. Unchecked exceptions don’t need to be declared in the method signature using the throws keyword if they can be thrown once the method is executed and propagate outside the method’s scope.
Let’s see an example:
public class RuntimeExceptionExample {
public static void main(String[] args) {
int[] arr = new int[20];
arr[20] = 20;
System.out.println(arr[20]);
}
}
After we run the above code, we get the following:
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 20
at com.baeldung.exception.exceptions_vs_errors.RuntimeExceptionExample.main(RuntimeExceptionExample.java:7)
As we can see, we got an ArrayIndexOutOfBoundsException which is a subclass of IndexOutOfBoundsException, which is itself a subclass of RuntimeException.
Other subclasses of RuntimeException include IllegalArgumentException, NullPointerException, and ArithmeticException.
4.2. Checked Exceptions
Other exceptions that are not subclasses of RuntimeException are checked exceptions. They need to be declared in the method signature using the throws keyword if they can be thrown once the method is executed and propagate outside the method’s scope:
public class CheckedExceptionExcample {
public static void main(String[] args) {
try (FileInputStream fis = new FileInputStream(new File("test.txt"))) {
fis.read();
} catch (IOException e) {
e.printStackTrace();
}
}
}
If we run the above code, we get the following:
java.io.FileNotFoundException: test.txt (No such file or directory)
at java.io.FileInputStream.open0(Native Method)
at java.io.FileInputStream.open(FileInputStream.java:195)
at java.io.FileInputStream.<init>(FileInputStream.java:138)
at com.baeldung.exception.exceptions_vs_errors.CheckedExceptionExcample.main(CheckedExceptionExcample.java:9)
We got a FileNotFoundException which is a subclass of IOException, which is a subclass of Exception.
TimeoutException and SQLException are other examples of checked exceptions.
5. Conclusion
In this article, we learned the differences between errors and exceptions in the Java ecosystem.
As always, the complete code samples are available over on GitHub.
#База знаний
- 24 фев 2021
-
13
Разбираемся, что такое исключения, зачем они нужны и как с ними работать.
vlada_maestro / shutterstock
Хлебом не корми — дай кому-нибудь про Java рассказать.
Из этой статьи вы узнаете:
- что такое исключения (Exceptions);
- как они возникают и чем отличаются от ошибок (Errors);
- зачем нужна конструкция try-catch;
- как разобраться в полученном исключении
- и как вызвать исключение самому.
Код вашей программы исправно компилируется и запускается, только вот вместо желанного результата вы видите непонятный текст. Строчки его будто кричат на вас, аж побагровели.
За примером далеко ходить не надо: сделаем то, что нам запрещали ещё в школе, — поделим на ноль.
public static void main(String[] args) {
hereWillBeTrouble(42, 0);
}
public static void hereWillBeTrouble(int a, int b) {
int oops = a / b;
System.out.println(oops);
}
А получим вот что:
Это и есть исключение.
«Исключение» — сокращение от слов «исключительный случай». Это ситуация, в которой программа не может продолжить работу или её работа становится бессмысленной. Причём речь не только о нештатных ситуациях — исключения бывают и намеренными, такие разработчик вызывает сам.
Это интересно. Исключения в Java появились уже в первой версии языка. А вот в языках, где их нет, вместо них возвращают коды ошибок.
У всех классов исключений есть общий класс-предок Throwable, от него наследуются классы Error и Exception, базовые для всех прочих.
Error — это критические условия, в которых работа программы должна быть завершена. Например, когда при выполнении программы закончилась память, произошёл сбой в системе или виртуальной машине. Не будем задерживаться на этой ветке, поскольку документация Java говорит:
Error is the superclass of all the exceptions from which ordinary programs are not ordinarily expected to recover.
Что в переводе означает: ошибки (Error) — это такие исключительные ситуации, в которых восстанавливать работу программы не предполагается.
То есть это проблемы, которые нельзя (недопустимо) исправлять на ходу. Всё, что нам остаётся, — извиниться перед пользователем и впредь писать программы, где возникнет меньше подобных ситуаций. Например, не допускать такой глубокой рекурсии, как в коде ниже:
static void notGood() {
System.out.println("Только не снова!");
notGood();
}
При работе этого метода у нас возникнет ошибка: Exception in thread «main» java.lang.StackOverflowError — стек вызовов переполнился, так как мы не указали условие выхода из рекурсии.
А теперь об Exception. Эти исключительные ситуации возникают, если разработчик допустил невыполнимую операцию, не предусмотрел особые случаи в бизнес-логике программы (или сообщает о них с помощью исключений).
1. Невыполнимая операция
Мир не рухнул, как в случае с Error, просто Java не знает, что делать дальше. Как раз из этого разряда деление на ноль в начале статьи: и правда, какое значение тогда присвоить переменной oops?
Убедитесь сами, что исключение класса ArithmeticException наследуется как раз от Exception.
Стоит запомнить. В IntelliJ IDEA, чтобы увидеть положение класса в иерархии, выберите его и нажмите Ctrl + H (или на пункт Type Hierarchy в меню Navigate).
Другая частая ситуация — обращение к несуществующему элементу массива. Например, у нас в нём десять элементов, а мы пытаемся обратиться к одиннадцатому.
2. Особый случай в бизнес-логике программы
Классика. Программируем задачу о перевозке волка, козы и капусты через реку: в лодке может быть только два пассажира, но волка с козой и козу с капустой нельзя оставлять на берегу вместе. Это и есть особый случай в бизнес-логике, который нельзя нарушать.
Или пользователь вводит дату начала некоторого периода и дату его окончания. Вторая дата не может быть раньше первой.
Или, допустим, у нас есть метод, который читает файл. Сам метод написан верно. Пользователь передал в него корректный путь. Только вот у этого работника нет права читать этот файл (его роль и права обусловлены предметной областью). Что же тогда методу возвращать? Вернуть-то нечего, ведь метод не отработал. Самое очевидное решение — выдать исключение.
В дерево исключений мы ещё углубимся, а сейчас посмотрим, что и как с ними делают.
Простейший вариант — ничего; возникает исключение — программа просто прекращает работать.
Чтобы убедиться в этом, выполним код:
public static void main(String[] args) {
hereWillBeTrouble(42, 0);
}
public static void hereWillBeTrouble(int a, int b) {
System.out.println("Всё, что было до...");
int oops = a / b;
System.out.println(oops);
System.out.println("Всё, что будет после...");
}
Так и есть: до деления на ноль код выполнялся, а после — нет.
Это интересно: когда возникает исключение, программисты выдают что-то вроде «код [вы]бросил исключение» или «код кинул исключение». А глагол таков потому, что все исключения — наследники класса Throwable, что значит «бросаемый» / «который можно бросить».
Второе, что можно делать с исключениями, — это их обрабатывать.
Для этого нужно заключить кусок кода, который может вызвать исключение, в конструкцию try-catch.
Как это работает: если в блоке try возникает исключение, которое указано в блоке catch, то исполнение блока try прервётся и выполнится код из блока catch.
Например:
public static void main(String[] args) {
hereWillBeTrouble();
}
private static void hereWillBeTrouble(int a, int b) {
int oops;
try {
System.out.println("Всё, что было до...");
oops = a / b;
System.out.println(oops);
System.out.println("Всё, что будет после...");
} catch (ArithmeticException e) {
System.out.println("Говорили же не делить на ноль!");
oops = 0;
}
System.out.println("Метод отработал");
}
Разберём этот код.
Если блок try кинет исключение ArithmeticException, то управление перехватит блок catch, который выведет строку «Говорили же не делить на ноль!», а значение oops станет равным 0.
После этого программа продолжит работать как ни в чём не бывало: выполнится код после блока try-catch, который сообщит: «Метод отработал».
Проверьте сами: запустите код выше. Вызовите метод hereWillBeTrouble с любыми значениями аргументов кроме нулевого b. Если в блоке try не возникнет исключений, то его код выполнится целиком, а в блок catch мы даже не попадём.
Есть ещё и третий вариант — пробросить исключение наверх. Но об этом в следующей статье.
Вернёмся к первой картинке. Посмотрим, что нам сказала Java, когда произошло исключение:
Начинаем разбирать сверху вниз:
— это указание на поток, в котором произошло исключение. В нашей простой однопоточной программе это поток main.
— какое исключение брошено. У нас это ArithmeticException. А java.lang.ArithmeticException — полное название класса вместе с пакетом, в котором он размещается.
— весточка, которую принесло исключение. Дело в том, что одно и то же исключение нередко возникает по разным причинам. И тут мы видим стандартное пояснение «/ by zero» — из-за деления на ноль.
— это самое интересное: стектрейс.
Стектрейс (Stack trace) — это упорядоченный список методов, сквозь которые исключение пронырнуло.
У нас оно возникло в методе hereWillBeTrouble на 8-й строке в классе Main (номер строки и класс указаны в скобках синим). А этот метод, в свою очередь, вызван методом main на 3-й строке класса Main.
Стектрейсы могут быть довольно длинными — из десятков методов, которые вызывают друг друга по цепочке. И они здорово помогают расследовать неожиданно кинутое исключение.
Советую закреплять теорию на практике. Поэтому вернитесь в блок про Error и вызовите метод notGood — увидите любопытный стектрейс.
Всё это время мы имели дело с исключением, которое бросает Java-машина — при делении на ноль. Но как вызвать исключение самим?
Раз исключение — это объект класса, то программисту всего-то и нужно, что создать объект с нужным классом исключения и бросить его с помощью оператора throw.
public static void main(String[] args) {
hereWillBeTrouble(42, 0);
}
private static void hereWillBeTrouble(int a, int b) {
if (b == 0) {
throw new ArithmeticException("ты опять делишь на ноль?");
}
int oops = a / b;
System.out.println(oops);
}
При создании большинства исключений первым параметром в конструктор можно передать сообщение — мы как раз сделали так выше.
А получим мы то же самое, что и в самом первом примере, только вместо стандартной фразы «/by zero» теперь выдаётся наш вопрос-пояснение «ты опять делишь на ноль?»:
В следующей статье мы углубимся в иерархию исключений Java, узнаем про их разделение на checked и unchecked, а также о том, что ещё интересного можно с ними делать.
Учись бесплатно:
вебинары по программированию, маркетингу и дизайну.
Участвовать
Школа дронов для всех
Учим программировать беспилотники и управлять ими.
Узнать больше
Improve Article
Save Article
Improve Article
Save Article
In java, both Errors and Exceptions are the subclasses of java.lang.Throwable class. Error refers to an illegal operation performed by the user which results in the abnormal working of the program. Programming errors often remain undetected until the program is compiled or executed. Some of the errors inhibit the program from getting compiled or executed. Thus errors should be removed before compiling and executing. It is of three types:
- Compile-time
- Run-time
- Logical
Whereas exceptions in java refer to an unwanted or unexpected event, which occurs during the execution of a program i.e at run time, that disrupts the normal flow of the program’s instructions.
Now let us discuss various types of errors in order to get a better understanding over arrays. As discussed in the header an error indicates serious problems that a reasonable application should not try to catch. Errors are conditions that cannot get recovered by any handling techniques. It surely causes termination of the program abnormally. Errors belong to unchecked type and mostly occur at runtime. Some of the examples of errors are Out of memory errors or System crash errors.
Example 1 Run-time Error
Java
class
StackOverflow {
public
static
void
test(
int
i)
{
if
(i ==
0
)
return
;
else
{
test(i++);
}
}
}
public
class
GFG {
public
static
void
main(String[] args)
{
StackOverflow.test(
5
);
}
}
Output:
Example 2
Java
class
GFG {
public
static
void
main(String args[])
{
int
a =
2
, b =
8
, c =
6
;
if
(a > b && a > c)
System.out.println(a
+
" is the largest Number"
);
else
if
(b > a && b > c)
System.out.println(b
+
" is the smallest Number"
);
else
System.out.println(c
+
" is the largest Number"
);
}
}
Output
8 is the smallest Number
Now let us dwell onto Exceptions which indicates conditions that a reasonable application might want to catch. Exceptions are the conditions that occur at runtime and may cause the termination of the program. But they are recoverable using try, catch and throw keywords. Exceptions are divided into two categories:
- Checked exceptions
- Unchecked exceptions
Checked exceptions like IOException known to the compiler at compile time while unchecked exceptions like ArrayIndexOutOfBoundException known to the compiler at runtime. It is mostly caused by the program written by the programmer.
Example Exception
Java
class
GFG {
public
static
void
main(String[] args)
{
int
a =
5
, b =
0
;
try
{
int
c = a / b;
}
catch
(ArithmeticException e) {
e.printStackTrace();
}
}
}
Output:
Finally now wrapping-off the article by plotting the differences out between them in a tabular format as provided below as follows:
Errors | Exceptions |
---|---|
Recovering from Error is not possible. | We can recover from exceptions by either using try-catch block or throwing exceptions back to the caller. |
All errors in java are unchecked type. | Exceptions include both checked as well as unchecked type. |
Errors are mostly caused by the environment in which program is running. | Program itself is responsible for causing exceptions. |
Errors can occur at compile time as well as run time. Compile Time: eg Syntax Error Run Time: Logical Error. |
All exceptions occurs at runtime but checked exceptions are known to the compiler while unchecked are not. |
They are defined in java.lang.Error package. | They are defined in java.lang.Exception package |
Examples : java.lang.StackOverflowError, java.lang.OutOfMemoryError | Examples : Checked Exceptions : SQLException, IOException Unchecked Exceptions : ArrayIndexOutOfBoundException, NullPointerException, ArithmeticException. |
Exception Handling is one of the most important aspect of Java programming language. There is no application without errors. Errors happen for various reasons, for example, out of memory, a remote server not responding, a file that is being attempted to open is not present, a duplicate record insert statement fired on a database that has a primary key, trying to read a non-existent array element and many more.
Error and Exception
Java separates errors into two categories, Error and Exception, both having respective classes. As per the documentation, an Error illustrates serious problems that a reasonable application should not try to catch. Exception illustrates conditions that a reasonable application might want to catch.
What they meant by catching? Both Error and Exception extend Throwable class. JVM can throw instances of Throwable class in case of exceptional scenarios. In Java, both Error and Exception are exceptional situations that break the normal flow of an application. When an exceptional situation arises, instead of breaking the application, Java creates an instance of Error or Exception object comprising the error details and throws it to your program. So you can catch that instance and determine what to do in that scenario. But how to catch a Throwable object?
try {
// Here goes the code that can throw Error or Exception
} catch (Throwable throwable) {
// Handle the error or exception
}
As depicted in the above code snippet, we write code that is likely to result in error inside the try block. If JVM encounters any error inside the try block, it creates an instance of type (or sub-type of) Throwable
and throws it using the throw
keyword, which gets caught by the catch block.
You can have multiple catch blocks as below to catch the Error or Exception object using the respective catch blocks.
try {
// Here goes the code that can throw Error or Exception
} catch (Exception ex) {
// Handle the exception
} catch (Error err) {
// Handle the error
}
We do not handle an Error; JVM throws them only when there is a serious problem and usually an application cannot recover from it. However, frequently we handle the occurrences of Exception.
Types of Exceptions
There are 2 types of Exceptions present: (1) Checked Exceptions (2) Unchecked Exceptions.
If a User defined exception extends RuntimeException it is called as unchecked exception, however if that user defined exception extends the Exception class, it will be called as checked exception.
Checked Exception
The compiler checks checked Exceptions during the compilation process. When the compiler finds that a particular line can throw a checked exception, it will show a compilation error and forces the developer to handle that exception explicitly. Checked exceptions are the exceptions derived from Exception class. Below is an example of Exception Handling of Checked Exceptions.
package com.techstackjournal.exceptions;import java.io.File;import java.io.FileReader;public class ExceptionHandlingExample1 { public static void main(String[] args) { FileReader fr = new FileReader(new File("file.txt")); }}
Exception in thread "main" java.lang.Error: Unresolved compilation problem:
Unhandled exception type FileNotFoundException
at com.techstackjournal.exceptions.ExceptionHandlingExample1.main(ExceptionHandlingExample1.java:10)
We are getting a compile error at line number 10. It says that there is an unhandled exception of type FileNotFoundException. You must wrap that line within try block and catch FileNotFoundException.
How a compiler comes to know if a line throws exception or not? It is simple, the calling method or constructor must be indicating that they throw an exception with a throws clause in their declaration.
In the above example, we are getting the compilation error due to the FileReader constructor. If you have a look at its signature, you’ll come to know that it throws a FileNotFoundException
, so we must handle it before instantiating FileReader.
public FileReader(File file) throws FileNotFoundException {
super(new FileInputStream(file));
}
Unchecked Exception
Unchecked exceptions are those exceptions that are unchecked by the compiler during the compilation but are thrown at runtime if that exceptional situation occurs. All Error and RuntimeExceptions fall under this category.
package com.techstackjournal.exceptions;
public class ExceptionHandlingExample2 {
public static void main(String[] args) {
int[] arr = { 10, 20, 30 };
System.out.println(arr[5]);
}
}
The above program will not give any compilation error, even though we are trying to access the array element which doesn’t exist. But when you run this code, you’ll get below exception that suggests that the index that we are passing to the array is out of bounds.
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 5
at com.techstackjournal.exceptions.ExceptionHandlingExample2.main(ExceptionHandlingExample2.java:7)
Exception Handling by Catching the Exception
- Now we know that JVM throws Exception objects and we need to wrap the code within try-catch blocks to catch those exception objects
- A
try
block may throw multiple exceptions and you can write multiplecatch
blocks next totry
, to catch all of them - Within the catch block, you usually log why there was an exception and continue to recover from it
- When there is an exception in the try block, the control is transferred to the relevant catch block, and the subsequent lines of code in the try block will never get executed
- Once the catch block is executed, control will move to the next executable statement in the stack, instead of going back to the line from where the exception has been thrown
- In case of an exception within a try block, only one relevant catch block gets executed, even though that try block has multiple catch blocks to handle multiple exceptions
- For example, if there is a try block exception handling with FileNotFoundException and ArrayIndexOutOfBounds catch handlers, only one of them will be executed based on the exception occurred and another catch block is just ignored
Finally Block
- Immediately after the
try-catch
block we can write afinally
block - It gets executed always irrespective of whether or not there is an exception
- We usually use it for cleanup activities.
package com.techstackjournal.exceptions;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
public class ExceptionHandlingExample3 {
public static void main(String[] args) {
String status = readFile();
switch (status) {
case "FILE_READ_COMPLETE":
System.out.println("Show success screen");
break;
case "FILE_NOT_FOUND":
System.out.println("Show error screen");
break;
}
}
private static String readFile() {
FileReader fr = null;
try {
fr = new FileReader(new File("file.txt"));
return "FILE_READ_COMPLETE";
} catch (FileNotFoundException e) {
System.out.println("File not found");
return "FILE_NOT_FOUND";
} finally {
try {
if (fr != null) {
fr.close();
}
} catch (IOException e) {
System.out.println("Error closing the file");
}
}
}
}
Why don’t we write the cleanup code in the catch block itself? If you encounter a different exception other than what you are handling, none of the catch blocks will get executed, resulting in a non-execution of the cleanup code. Also, if you start writing cleanup code in a catch block, you will soon end up duplicating the code in multiple catch blocks. Someone may also miss adding that cleanup code to the new catch blocks which they are adding at a later stage. So, in short, if you put code cleanup in the catch block, it adversely affects the code maintainability.
The sequence of catch and catching polymorphically
Similar to methods and constructors, a catch block can also receive exception objects of a class and its sub-types polymorphically. That means, you can handle all exceptions using a single catch block with Exception class, which is a bad exception-handling technique. You may add the parent class exception class as the last resort after handling all the specific exceptions possible in that try block.
While adding multiple catch blocks, you must handle them in child to parent sequence, that is most specific exception to broad level exception. If you try adding broad level exception first, you will get a compilation error saying the granular exceptions are unreachable.
package com.techstackjournal.exceptions;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
public class ExceptionHandlingExample4 {
public static void main(String[] args) {
String status = readFile();
switch (status) {
case "FILE_READ_COMPLETE":
System.out.println("Show success screen");
break;
case "FILE_NOT_FOUND":
System.out.println("Show file not found screen");
break;
case "ERROR_OCCURED":
System.out.println("Show error screen");
break;
}
}
private static String readFile() {
FileReader fr = null;
try {
fr = new FileReader(new File("D:\file.txt"));
System.out.println("First letter: " + (char) fr.read());
return "FILE_READ_COMPLETE";
} catch (FileNotFoundException ex) {
System.out.println("File not found");
return "FILE_NOT_FOUND";
} catch (IOException ex) {
System.out.println("IO exception occured");
return "ERROR_OCCURED";
} catch (Exception ex) {
System.out.println("Something wrong");
return "ERROR_OCCURED";
} finally {
try {
if (fr != null) {
fr.close();
}
} catch (IOException e) {
System.out.println("Error closing the file");
}
}
}
}
I’ve updated the previous code to add new catch blocks with IOException and then Exception. If specific exception FileNotFoundException or IOException is thrown, respective catch block will handle it. But if there is any other exception, catch with Exception block will handle it.
Propagating Exceptions
Allowing exceptions to spill over to the calling method by not handling it, is called as propagating of exceptions.
You don’t have to handle each exception by suppressing the exception details, especially when you are creating APIs for others to consume. By propagating the exception, you are giving control to the calling application to handle the exception in their preferred way.
But wait, if you don’t handle a checked exception, your application will not even get compiled. This can be addressed by the declaration of exceptions in the method signature.
Declaration of Exceptions in Method Signature
When you don’t want to handle exceptions, you can declare those exceptions in the method declaration, passing the buck to the caller.
private static String readFile() throws FileNotFoundException, IOException {
// Code
}
This way, the compiler knows that your method may throw exceptions and it will check if calling application is handling them.
Now calling application has 2 options to deal with the exception. (1) wrap the method call with try-catch
(2) declare the exceptions in its method to propagate to its calling method.
package com.techstackjournal.exceptions;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
public class ExceptionHandlingExample5 {
public static void main(String[] args) {
try {
char firstChar = readFile("D:\file.txt");
System.out.println("First letter: " + firstChar);
System.out.println("Show success screen");
} catch (FileNotFoundException e) {
System.out.println("Show file not found screen");
} catch (IOException e) {
System.out.println("Show error screen");
}
}
private static char readFile(String filePath) throws FileNotFoundException, IOException {
FileReader fr = new FileReader(new File(filePath));
char firstChar = (char) fr.read();
fr.close();
return firstChar;
}
}
I’ve updated our previous example by declaring exceptions in readFile
method signature to propagate exceptions to the calling method. JVM will raise an exception, in case the given filePath
is not found. But the calling method main
handles that exception by surrounding the readFile
method call within try-catch
block.
If you want to handle the checked exception, wrap it inside try-catch, else declare all the checked exceptions that the method will throw.
Throwing Exceptions
Till this point, we are catching the exceptions that are being thrown by JVM. JVM uses throw keyword to throw an Exception when an exceptional situation occurred. Similarly, we can also throw exceptions based on the situation.
In this sample program, we want to verify whether the email contains @ symbol or not. If @ symbol is present all is good, but if @ symbol is not present we want to throw an exception.
package com.techstackjournal.exceptions;
public class ExceptionHandlingExample6 {
public static void main(String[] args) {
try {
parseEmail("admintechstackjournal.com");
System.out.println("Email parsed successfully");
} catch (Exception e) {
e.printStackTrace();
}
}
private static void parseEmail(String email) throws Exception {
if (email.indexOf("@") == -1) {
throw new Exception("Invalid email");
}
}
}
You can see that when @ symbol is not present, we are instantiating the Exception and throwing that instance using the throw
keyword. General practice is that we throw custom exceptions in this kind of situation instead of throwing API provided exceptions.
java.lang.Exception: Invalid email
at com.techstackjournal.exceptions.ExceptionHandlingExample6.parseEmail(ExceptionHandlingExample6.java:15)
at com.techstackjournal.exceptions.ExceptionHandlingExample6.main(ExceptionHandlingExample6.java:7)
Custom Exception
By extending from an Exception
or any subclass of Exception
class, we can create custom exceptions. If you derive the new exception from Exception
class, since Exception
class is a checked exception, the new custom exception class will also be a checked exception. If you derive the new exception from RuntimeException
class, it will be an unchecked exception.
package com.techstackjournal.exceptions;
public class InvalidEmailException extends Exception {
public InvalidEmailException(String email) {
super("Invalid email: "+email);
}
}
Our custom exception class inherit methods such as getMessage
and printStackTrace
from the super class Throwable
.
package com.techstackjournal.exceptions;
public class ExceptionHandlingExample8 {
public static void main(String[] args) {
try {
parseEmail("admintechstackjournal.com");
System.out.println("Email parsed successfully");
} catch (InvalidEmailException e) {
e.printStackTrace();
}
}
private static void parseEmail(String email) throws InvalidEmailException {
if (email.indexOf("@") == -1) {
throw new InvalidEmailException(email);
}
}
}
Similar to our earlier example, we are throwing our custom exception InvalidEmailException
when email doesn’t contain the @ symbol.
Horrors of Catching and Throwing Exceptions
I saw many programmers handle exceptions using catch block, log the exception and throw a custom application exception. This is such a poor practice that it will create a nightmare situation if there is an exception in production. For troubleshooting, if you look at the logs, you will only see the line number from where the custom exception is being thrown. We will lose the original exception details with this approach.
package com.techstackjournal.exceptions;
public class ApplicationException extends Exception {
public ApplicationException() {
super("Unexpected problem occurred");
}
}
package com.techstackjournal.exceptions;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
public class ExceptionHandlingExample9 {
public static void main(String[] args) {
try {
char firstChar = readFile();
System.out.println("First letter: " + firstChar);
} catch (ApplicationException e) {
e.printStackTrace();
}
}
private static char readFile() throws ApplicationException {
FileReader fr = null;
char firstChar;
try {
fr = new FileReader(new File("D:\file2.txt"));
System.out.println(fr);
firstChar = (char) fr.read();
fr.close();
} catch (IOException ex) {
System.out.println("File not found");
throw new ApplicationException();
}
return firstChar;
}
}
In the above program, we are throwing ApplicationException if JVM throws FileNotFoundException. When “file1.txt” is not present, we will get below output.
File not found
com.techstackjournal.exceptions.ApplicationException: Unexpected problem occurred
at com.techstackjournal.exceptions.ExceptionHandlingExample9.readFile(ExceptionHandlingExample9.java:28)
at com.techstackjournal.exceptions.ExceptionHandlingExample9.main(ExceptionHandlingExample9.java:11)
It suppresses the original source of the exception at Line number 22.
Correct way of Throwing Custom Exceptions
Do not suppress the original exceptions thrown by JVM with custom exception.
If you had to suppress the original exception with your custom exception, prefer passing the original exception as cause to the new exception.
package com.techstackjournal.exceptions;
public class ApplicationException extends Exception {
public ApplicationException(Throwable t) {
super(t);
}
}
package com.techstackjournal.exceptions;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
public class ExceptionHandlingExample9 {
public static void main(String[] args) {
try {
char firstChar = readFile();
System.out.println("First letter: " + firstChar);
} catch (ApplicationException e) {
e.printStackTrace();
}
}
private static char readFile() throws ApplicationException {
FileReader fr = null;
char firstChar;
try {
fr = new FileReader(new File("D:\file1.txt"));
System.out.println(fr);
firstChar = (char) fr.read();
fr.close();
} catch (IOException ex) {
System.out.println("File not found");
throw new ApplicationException(ex);
}
return firstChar;
}
}
In Java, the cause of an Exception can be because of another Exception. The super class Throwable contains an attribute called cause
, which is nothing but another Throwable. This signifies the cause of the current Exception.
In the above program, we are passing the original exception as a cause to the ApplicationException
constructor. When we follow this approach, the original exception will not get suppressed instead it will be printed in logs as the cause of the ApplicationException as below.
File not found
com.techstackjournal.exceptions.ApplicationException: java.io.FileNotFoundException: D:file1.txt (The system cannot find the file specified)
at com.techstackjournal.exceptions.ExceptionHandlingExample9.readFile(ExceptionHandlingExample9.java:28)
at com.techstackjournal.exceptions.ExceptionHandlingExample9.main(ExceptionHandlingExample9.java:11)
Caused by: java.io.FileNotFoundException: D:file1.txt (The system cannot find the file specified)
at java.io.FileInputStream.open0(Native Method)
at java.io.FileInputStream.open(FileInputStream.java:195)
at java.io.FileInputStream.<init>(FileInputStream.java:138)
at java.io.FileReader.<init>(FileReader.java:72)
at com.techstackjournal.exceptions.ExceptionHandlingExample9.readFile(ExceptionHandlingExample9.java:22)
... 1 more
If the intention is to log the exception before you want to throw it back to the calling application, you can throw the original exception itself starting from Java 7.
Handle and Re-throw an Exception
Java allows you to re-throw an exception from within the catch block to the calling method. To re-throw an exception, you do not have to use the new keyword. You just need to throw whatever object you received in the catch block.
Since we are re-throwing exceptions, we need to declare those exceptions in the method signature
The beauty of doing this is, you can perform any post exception activities and re-throw it to the calling method. Before Java 7, we used to have only one option either to handle it or to throw it.
Best thing is, the stack trace still shows the original line number where the exception has occurred
package com.techstackjournal.exceptions;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
public class ExceptionHandlingExample7 {
public static void main(String[] args) {
try {
char firstChar = readFile();
System.out.println("First letter: " + firstChar);
} catch (IOException e) {
e.printStackTrace();
}
}
private static char readFile() throws IOException {
FileReader fr = null;
char firstChar;
try {
fr = new FileReader(new File("D:\file1.txt"));
System.out.println(fr);
firstChar = (char) fr.read();
fr.close();
} catch (IOException ex) {
System.out.println("IO exception occured");
throw ex;
}
return firstChar;
}
}
In the above program, readFile
method is catching FileNotFoundException
and IOException
, but after printing the error message we are throwing them back to the calling program.
So, when file1.txt doesn’t exist, we get below output:
IO exception occured
java.io.FileNotFoundException: D:file1.txt (The system cannot find the file specified)
at java.io.FileInputStream.open0(Native Method)
at java.io.FileInputStream.open(FileInputStream.java:195)
at java.io.FileInputStream.<init>(FileInputStream.java:138)
at java.io.FileReader.<init>(FileReader.java:72)
at com.techstackjournal.exceptions.ExceptionHandlingExample7.readFile(ExceptionHandlingExample7.java:23)
at com.techstackjournal.exceptions.ExceptionHandlingExample7.main(ExceptionHandlingExample7.java:12)
As developers, we would like our users to interact with applications that run smoothly and without issues. We want the libraries that we create to be widely adopted and successful. All of that will not happen without the code that handles errors.
Java exception handling is often a significant part of the application code. You might use conditionals to handle cases where you expect a certain state and want to avoid erroneous execution – for example, division by zero. However, there are cases where conditions are not known or are well hidden because of third party code. In other cases, code is designed to create an exception and throw it up the stack to allow for the graceful handling of a Java error at a higher level.
If all this is new to you, this tutorial will help you understand what Java exceptions are, how to handle them and the best practices you should follow to maintain the natural flow of the application.
What Is an Exception in Java?
An Exception is an event that causes your normal Java application flow to be disrupted. Exceptions can be caused by using a reference that is not yet initialized, dividing by zero, going beyond the length of an array, or even JVM not being able to assign objects on the heap.
In Java, an exception is an Object that wraps the error that caused it and includes information like:
- The type of error that caused the exception with the information about it.
- The stack trace of the method calls.
- Additional custom information that is related to the error.
Various Java libraries throw exceptions when they hit a state of execution that shouldn’t happen – from the standard Java SDK to the enormous amounts of open source code that is available as a third-party library.
Exceptions can be created automatically by the JVM that is running our code or explicitly our code itself. We can extend the classes that are responsible for exceptions and create our own, specific Java exceptions that handle unexpected situations. But keep in mind that throwing an exception doesn’t come for free. It is expensive. The larger the call stack the more expensive the exception.
Exception handling in Java is a mechanism to process, catch and throw Java errors that signal an abnormal state of the program so that the execution of the business code can continue uninterrupted.
Why Handle Java Exceptions?
Using Java exceptions is very handy; they let you separate the business logic from the application code for handling errors. However, exceptions are neither free nor cheap. Throwing an exception requires the JVM to perform operations like gathering the full stack trace of the method calls and passing it to the method that is responsible for handling the exception. In other words, it requires additional resources compared to a simple conditional.
No matter how expensive the exceptions are, they are invaluable for troubleshooting issues. Correlating exceptions with other data like JVM metrics and various application logs can help with resolving your problems fast and in a very efficient manner.
Exception Class Hierarchy
Java is an object-oriented language, and thus, every class will extend the java.lang.Object. The same goes for the Throwable class, which is the base class for the errors and exceptions in Java. The following picture illustrates the class hierarchy of the Throwable class and its most common subclasses:
The Difference Between a Java Exception and Error
Before going into details about the Exception class, we should ask one fundamental question: What is the difference between an Error and an Exception in Java?
To answer the question, let’s look at Javadocs and start from the Throwable class. The Throwable class is the mother of all errors–the superclass of all errors and exceptions that your Java code can produce. Only objects that are instances of the Throwable class or any of its subclasses can be thrown by the code running inside the JVM or can be declared in the methods throw clause. There are two main implementations of the Throwable class.
A Java Error is a subclass of Throwable that represents a serious problem that a reasonable application should not try to catch. The method does not have to declare an Error or any of its subclasses in its throws clause for it to be thrown during the execution of a method. The most common subclasses of the Error class are Java OutOfMemoryError and StackOverflowError classes. The first one represents JVM not being able to create a new object; we discussed potential causes in our article about the JVM garbage collector. The second error represents an issue when the application recurses too deeply.
The Java Exception and its subclasses, on the other hand, represent situations that an application should catch and try to handle. There are lots of implementations of the Exception class that represent situations that your application can gracefully handle. The FileNotFoundException, SocketException, or NullPointerException are just a few examples that you’ve probably come across when writing Java applications.
Types of Java Exceptions
There are multiple implementations of the Exception class in Java. They come from the Java Development Kit itself, but also from various libraries and applications that you might be using when writing your own code. We will not discuss every Exception subclass that you can encounter, but there are two main types that you should be aware of to understand how exception handling works in Java–the checked and unchecked, aka runtime exceptions.
Checked Exceptions
The Java checked exceptions are the ones that implement the Exception class and not the RuntimeException. They are called checked exceptions because the compiler verifies them during the compile-time and refuses to compile the code if such exceptions are not handled or declared. Instead, the compiler will notify you to handle these exceptions. For example, the FileNotFoundException is an example of such an exception.
In order for a method or constructor to throw a checked exception it needs to be explicitly declared:
public CheckedExceptions() throws FileNotFoundException { throw new FileNotFoundException("File not found"); } public void throwsExample() throws FileNotFoundException { throw new FileNotFoundException("File not found"); }
You can see that in both cases we explicitly have the throws clause mentioning the FileNotFoundException that can be thrown by the execution of the constructor of the throwsExample method. Such code will be successfully compiled and can be executed.
On the other hand, the following code will not compile:
public void wrongThrowsExample() { throw new FileNotFoundException("File not found"); }
The reason for the compiler not wanting to compile the above code will be raising the checked exception and not processing it. We need to either include the throws clause or handle the Java exception in the try/catch/finally block. If we were to try to compile the above code as is the following error would be returned by the compiler:
/src/main/java/com/sematext/blog/java/CheckedExceptions.java:18: error: unreported exception FileNotFoundException; must be caught or declared to be thrown throw new FileNotFoundException("File not found"); ^
Unchecked Exceptions / Runtime Exceptions
The unchecked exceptions are exceptions that the Java compiler does not require us to handle. The unchecked exceptions in Java are the ones that implement the RuntimeException class. Those exceptions can be thrown during the normal operation of the Java Virtual Machine. Unchecked exceptions do not need to be declared in the method throws clause in order to be thrown. For example, this block of code will compile and run without issues:
public class UncheckedExceptions { public static void main(String[] args) { UncheckedExceptions ue = new UncheckedExceptions(); ue.run(); } public void run() { throwRuntimeException(); } public void throwRuntimeException() { throw new NullPointerException("Null pointer"); } }
We create an UncheckedExceptions class instance and run the run method. The run method calls the throwRuntimeException method which creates a new NullPointerException. Because the NullPointerException is a subclass of the RuntimeException we don’t need to declare it in the throws clause.
The code compiles and propagates the exception up to the main method. We can see that in the output when the code is run:
Exception in thread "main" java.lang.NullPointerException: Null pointer at com.sematext.blog.java.UncheckedExceptions.throwRuntimeException(UncheckedExceptions.java:14) at com.sematext.blog.java.UncheckedExceptions.run(UncheckedExceptions.java:10) at com.sematext.blog.java.UncheckedExceptions.main(UncheckedExceptions.java:6)
This is exactly what we expected.
The most common subclasses of the Java RuntimeException that you probably saw already are ClassCastException, ConcurrentModificationException, IllegalArgumentException, or everyone’s favorite, NullPointerException.
Let’s now look at how we throw exceptions in Java.
How to Handle Exceptions in Java: Code Examples
Handling exceptions in Java is a game of using five keywords that combined give us the possibility of handling errors – the try, catch, finally, throw, and throws.
The first one – try is used to specify the block of code that can throw an exception:
try { File file = openNewFileThatMayNotExists(location); // process the file }
The try block must be followed by the catch or finally blocks. The first one mentioned can be used to catch and process exceptions thrown in the try block:
try { ... } catch (IOException ioe) { // handle missing file }
The finally block can be used to handle the code that needs to be executed regardless of whether the exception happened or not.
The throw keyword is used to create a new Exception instance and the throws keyword is used to declare what kind of exceptions can be expected when executing a method.
When handling exceptions in Java, we don’t want to just throw the created exception to the top of the call stack, for example, to the main method. That would mean that each and every exception that is thrown would crash the application and this is not what should happen. Instead, we want to handle Java exceptions, at least the ones that we can deal with, and either help fixing the problem or fail gracefully.
Java gives us several ways to handle exceptions.
Throwing Exceptions
An Exception in Java can be handled by using the throw keyword and creating a new Exception or re-throwing an already created exception. For example, the following very naive and simple method creates an instance of the File class and checks if the file exists. If the file doesn’t exist the method throws a new IOException:
public File openFile(String path) throws IOException { File file = new File(path); if (!file.exists()) { throw new IOException(String.format("File %s doesn't exist", path)); } // continue execution of the business logic return file; }
You can also re-throw a Java exception that was thrown inside the method you are executing. This can be done by the try-catch block:
public class RethrowException { public static void main(String[] args) throws IOException { RethrowException exec = new RethrowException(); exec.run(); } public void run() throws IOException { try { methodThrowingIOE(); } catch (IOException ioe) { // do something about the exception throw ioe; } } public void methodThrowingIOE() throws IOException { throw new IOException(); } }
You can see that the run method re-throws the IOException that is created in the methodThrowingIOE. If you plan on doing some processing of the exception, you can catch it as in the above example and throw it further. However, keep in mind that in most cases, this is not a good practice. You shouldn’t catch the exception, process it, and push it up the execution stack. If you can’t process it, it is usually better to pack it into a more specialized Exception class, so that a dedicated error processing code can take care of it. You can also just decide that you can’t process it and just throw it:
public class RethrowException { public static void main(String[] args) throws IOException { RethrowException exec = new RethrowException(); exec.run(); } public void run() throws IOException { try { methodThrowingIOE(); } catch (IOException ioe) { throw ioe; } } public void methodThrowingIOE() throws IOException { throw new IOException(); } }
There are additional cases, but we will discuss them when talking about how to catch exceptions in Java.
Try-Catch Block
The simplest and most basic way to handle exceptions is to use the try – catch block. The code that can throw an exception is put into the try block and the code that should handle it is in the catch block. The exception can be either checked or unchecked. In the first case, the compiler will tell you which kind of exceptions need to be caught or defined in the method throws clause for the code to compile. In the case of unchecked exceptions, we are not obligated to catch them, but it may be a good idea – at least in some cases. For example, there is a DOMException or DateTimeException that indicate issues that you can gracefully handle.
To handle the exception, the code that might generate an exception should be placed in the try block which should be followed by the catch block. For example, let’s look at the code of the following openFileReader method:
public Reader openFileReader(String filePath) { try { return new FileReader(filePath); } catch (FileNotFoundException ffe) { // tell the user that the file was not found } return null; }
The method tries to create a FileReader class instance. The constructor of that class can throw a checked FileNotFoundException if the file provided as the argument doesn’t exist. We could just include the FileNotFoundException in the throws clause of our openFileReader method or catch it in the catch section just like we did. In the catch section, we can do whatever we need to get a new file location, create a new file, and so on.
If we would like to just throw it further up the call stack the code could be further simplified:
public Reader openFileReaderWithThrows(String filePath) throws FileNotFoundException { return new FileReader(filePath); }
This passes the responsibility of handling the FIleNotFoundException to the code that calls it.
Multiple Catch Block
We are not limited to a single catch block in the try-catch block. We can have multiple try-catch blocks that allow you to handle each exception differently, would you need that. Let’s say that we have a method that lists more than a single Java exception in its throws clause, like this:
public void readAndParse(String file) throws FileNotFoundException, ParseException { // some business code }
In order to compile and run this we need multiple catch blocks to handle each of the listed Java exceptions:
public void run(String file) { try { readAndParse(file); } catch (FileNotFoundException ex) { // do something when file is not found } catch (ParseException ex) { // do something if the parsing fails } }
Such code organization can be used when dealing with multiple Java exceptions that need to be handled differently. Hypothetically, the above code could ask for a new file location when the FileNotFoundException happens and inform the user of the issues with parsing when the ParseException happens.
There is one more thing we should mention when it comes to handle exceptons using multiple catch blocks. You need to remember that the order of the catch blocks matters. If you have a more specialized Java exception in the catch block after the more general exception the more specialized catch block will never be executed. Let’s look at the following example:
public void runTwo(String file) { try { readAndParse(file); } catch (Exception ex) { // this block will catch all exceptions } catch (ParseException ex) { // this block will not be executed } }
Because the first catch block catches the Java Exception the catch block with the ParseException will never be executed. That’s because the ParseException is a subclass of the Exception.
Catching Multiple Exceptions
To handle multiple Java exceptions with the same logic, you can list them all inside a single catch block. Let’s redo the above example with the FileNotFoundException and the ParseException to use a single catch block:
public void runSingleCatch(String file) { try { readAndParse(file); } catch (FileNotFoundException | ParseException ex) { // do something when file is not found } }
You can see how easy it is–you can connect multiple Java exceptions together using the | character. If any of these exceptions is thrown in the try block the execution will continue inside the catch block.
The Finally Block
In addition to try and the catch blocks, there is one additional section that may come in handy when handling exceptions in Java–the finally block. The finally block is the last section in your try-catch-finally expression and will always get executed–either after the try or after the catch. It will be executed even if you use the return statement in the try block, which by the way, you shouldn’t do.
Let’s look at the following example:
public void exampleOne() { FileReader reader = null; try { reader = new FileReader("/tmp/somefile"); // do some processing } catch (FileNotFoundException ex) { // do something } finally { if (reader != null) { try { reader.close(); } catch (IOException ex) { // do something } } } }
You can see the finally block. Keep in mind that you can have at most one finally block present. The way this code will be executed is as follows:
- The JVM will execute the try section first and will try to create the FileReader instance.
- If the /tmp/somefile is present and readable the try block execution will continue.
- If the /tmp/somefile is not present or is not readable the FileNotFoundException will be raised and the execution of the code will go to the catch block.
- After 2) or 3) the finally block will be executed and will try to close the FileReader instance if it is present.
The finally section will be executed even if we modify the try block and include the return statement. The following code does exactly the same as the one above despite having the return statement at the end of the try block:
public void exampleOne() { FileReader reader = null; try { reader = new FileReader("/tmp/somefile"); // do something return; } catch (FileNotFoundException ex) { // do something } finally { if (reader != null) { try { reader.close(); } catch (IOException ex) { // do something } } } }
So, why and when should you use the finally block to handle exceptions in Java? Well, it is a perfect candidate for cleaning up resources, like closing objects, calculating metrics, including Java logs about operation completion, and so on.
The Try-With-Resources Block
The last thing when it comes to handling exceptions in Java is the try-with-resources block. The idea behind that Java language structure is opening resources in the try section that will be automatically closed at the end of the statement. That means that instead of including the finally block we can just open the resources that we need in the try section and rely on the Java Virtual Machine to deal with the closing of the resource.
For the class to be usable in the try-with-resource block it needs to implement the java.lang.AutoCloseable, which includes every class implementing the java.io.Closeable interface. Keep that in mind.
An example method that reads a file using the FileReader class which uses the try-with-resources might look as follows:
public void readFile(String filePath) { try (FileReader reader = new FileReader(filePath)) { // do something } catch (FileNotFoundException ex) { // do something when file is not found } catch (IOException ex) { // do something when issues during reader close happens } }
Of course, we are not limited to a single resource and we can have multiple of them, for example:
public void readFiles(String filePathOne, String filePathTwo) { try ( FileReader readerOne = new FileReader(filePathOne); FileReader readerTwo = new FileReader(filePathTwo); ) { // do something } catch (FileNotFoundException ex) { // do something when file is not found } catch (IOException ex) { // do something when issues during reader close happens } }
One thing that you have to remember is that you need to process the Java exceptions that happen during resources closing in the catch section of the try-catch-finally block. That’s why in the above examples, we’ve included the IOException in addition to the FileNotFoundException. The IOException may be thrown during FileReader closing and we need to process it.
Catching User-Defined Exceptions
The Throwable and Exception are Java classes and so you can easily extend them and create your own exceptions. Depending on if you need a checked or unchecked exception you can either extend the Exception or the RuntimeException class. To give you a very simple example on how such code might look like, have a look at the following fragment:
public class OwnExceptionExample { public void doSomething() throws MyOwnException { // code with very important logic throw new MyOwnException("My Own Exception!"); } class MyOwnException extends Exception { public MyOwnException(String message) { super(message); } } }
In the above trivial example we have a new Java Exception implementation called MyOwnException with a constructor that takes a single argument – a message. It is an internal class, but usually you would just move it to an appropriate package and start using it in your application code.
How to Catch Specific Java Exceptions
Catching specific Java exceptions is not very different from handling a general exception. Let’s look at the code examples. Catching exceptions that implement the Exception class is as simple as having a code similar to the following one:
try { // code that can result in exception throwing } catch (Exception ex) { // handle exception }
The catch block in the above example would run for every object implementing the Exception class. These include IOException, EOFException, FileNotFoundException, or the NullPointerException. But sometimes we may want to have different behavior depending on the exception that was thrown. You may want different behavior for the IOException and different for the FileNotFoundException. This can be achieved by having multiple catch blocks:
try { // code that can result in exception throwing } catch (FileNotFoundException ex) { // handle exception } catch (IOException ex) { // handle exception }
There is one thing to remember though. The first catch clause that will match the exception class will be executed. The FileNotFoundException is a subclass of the IOException. That’s why I specified the FileNotFoundException as the first one. If I would do the opposite, the block with the IOException handling would catch all IOException instances and their subclasses. Something worth remembering.
Finally, we’ve mentioned that before, but I wanted to repeat myself here – if you would like to handle numerous exceptions with the same code you can do that by combining them in a single catch block:
try { // code that can result in exception throwing } catch (FileNotFoundException | EOFException ex) { // handle exception } catch (IOException ex) { // handle exception }
That way you don’t have to duplicate the code.
Java Exception Handling Best Practices
There are a lot of best practices when it comes to handling exceptions in the Java Virtual Machine world, but I find a few of them useful and important.
Keep Exceptions Use to a Minimum
Every time you throw an Exception it needs to be handled by the JVM. Throwing an exception doesn’t cost much, but for example, getting the stack trace for the exception is noticeable, especially when you deal with a large number of concurrent requests and get the stack trace for every exception.
What I will say is not true for every situation and for sure makes the code a bit uglier, but if you want to squeeze every bit of performance from your code, try to avoid Java exceptions when a simple comparison is enough. Look at the following method:
public int divide(int dividend, int divisor) { try { return dividend / divisor; } catch (ArithmeticException ex) { LOG.error("Error while division, stack trace:", ex.getStackTrace()); } return -1; }
It tries to divide two numbers and catches the ArithmeticException in case of a Java error. What kind of error can happen here? Well, division by zero is the perfect example. If the divisor argument is 0 the code will throw an exception, catch it to avoid failure in the code and continue returning -1. This is not the perfect way to go since throwing a Java exception has a performance penalty – we talk about it later in the blog post. In such cases, you should check the divisor argument and allow the division only if it is not equal to 0, just like this:
public int divide(int dividend, int divisor) { if (divisor != 0) { return 10 / divisor; } return -1; }
The performance of throwing an exception is not high, but parts of the error handling code may be expensive – for example stack trace retrieval. Keep that in mind when writing your code that handles Java exceptions and situations that should not be allowed.
Always Handle Exceptions, Don’t Ignore Them
Always handle the Java Exception, unless you don’t care about one. But if you think that you should ignore one, think twice. The methods that you call throw exceptions for a reason and you may want to process them to avoid problematic situations.
Process the exceptions, log them, or just print them to the console. Avoid doing things you can see in the following code:
try { FileReader reader = new FileReader(filePath); // some business code } catch (FileNotFoundException ex) {}
As you can see in the code above the FileNotFoundException is hidden and we don’t have any idea that the creation of the FileReader failed. Of course, the code that runs after the creation of the FileReader would probably fail if it were to operate on that. Now imagine that you are catching Java exceptions like this:
try { FileReader reader = new FileReader(filePath); // some business code } catch (Exception ex) {}
You would catch lots of Java exceptions and completely ignore them all. The least you would like to do is fail and log the information so that you can easily find the problem when doing log analysis or setting up an alert in your log centralization solution. Of course, you may want to process the exception, maybe do some interaction with external systems or the user itself, but for sure not hide the problem.
If you really don’t want to handle an exception in Java you can just print it to the log or error stream, for example:
try { FileReader reader = new FileReader(filePath); // some business code } catch (Exception ex) { ex.printStackTrace(); }
Or even better, if you are using a centralized logging solution just do the following to store the error log there:
try { FileReader reader = new FileReader(filePath); // some business code } catch (Exception ex) { LOG.error("Error during FileReader creation", ex); }
Use Descriptive Messages When Throwing Exceptions
When using exceptions, think about the person who will be looking at the logs or will be troubleshooting your application. Think about what kind of information will be needed to quickly and efficiently find the root cause of the problem – Java exceptions in our case.
When throwing an exception you can use code like this:
throw new FileNotFoundException(“file not found”);
Or code that looks like this:
throw new FileNotFoundException(String.format(“File %s not found in directory %s”, file, directory));
The first one will just say that some mystery file was not found. The person that will try to diagnose and fix the problem will not know which and where the file is expected to be. The second example explicitly mentions which file is expected and where – and it is exactly what you should do when throwing exceptions.
Never Use Return Statement in the Finally Block
The finally block is the place in which you can execute code regardless if the exception happened or not – for example close the opened resources to avoid leaks. The next thing you should avoid is using the return statement in the finally block of your code. Have a look at the following code fragment:
public class ReturnInFinally { public static void main(String[] args) { ReturnInFinally app = new ReturnInFinally(); app.example(); System.out.println("Ended without error"); } public void example() { try { throw new NullPointerException(); } finally { return; } } }
If you were to run it, it would end up printing the Ended without error to the console. But we did throw the NullPointerException, didn’t we? Yes, but that would be hidden because of our finally block. We didn’t catch the exception and according to Java language Exception handling specification, the exception would just be discarded. We don’t want something like that to happen, because that would mean that we are hiding issues in the code and its execution.
Never Use Throw Statement in the Finally Block
A very similar situation to the one above is when you try to throw a new Java Exception in the finally block. Again, the code speaks louder than a thousand words, so let’s have a look at the following example:
public class ThrowInFinally { public static void main(String[] args) { ThrowInFinally app = new ThrowInFinally(); app.example(); } public void example() { try { throw new RuntimeException("Exception in try"); } finally { throw new RuntimeException("Exception in finally"); } } }
If you were to execute the above code the sentence that will be printed in the error console would be the following:
Exception in thread "main" java.lang.RuntimeException: Exception in finally at com.sematext.blog.java.ThrowInFinally.example(ThrowInFinally.java:13) at com.sematext.blog.java.ThrowInFinally.main(ThrowInFinally.java:6)
Yes, it is true. The Java RuntimeException that was thrown in the try block will be hidden and the one that was thrown in the finally block will take its place. It may not seem big, but have a look at the code like this:
public void example() throws Exception { FileReader reader = null; try { reader = new FileReader("/tmp/somefile"); } finally { reader.close(); } }
Now think about the execution of the code and what happens in a case where the file specified by the filePath is not available. First, the FileReader constructor would throw a FileNotFoundException and the execution would jump to the finally block. Once it gets there it would try to call the close method on a reference that is null. That means that the NullPointerException would be thrown here and the whole example method would throw it. So practically, the FileNotFoundException would be hidden and thus the real problem would not be easily visible that well.
Performance Side Effects of Using and Handling Exceptions in Java
We’ve mentioned that using exceptions in Java doesn’t come for free. Throwing an exception requires the JVM to fill up the whole call trace, list each method that comes with it, and pass it further to the code that will finally catch and handle the Java exception. That’s why you shouldn’t use exceptions unless it is really necessary.
Let’s look at a very simple test. We will run two classes–one will do the division by zero and catch the exception that is caused by that operation. The other code will check if the divisor is zero before throwing the exception. The mentioned code is encapsulated in a method.
The code that uses an exception looks as follows:
public int divide(int dividend, int divisor) { try { return dividend / divisor; } catch (Exception ex) { // handle exception } return -1; }
The code that does a simple check using a conditional looks as follows:
public int divide(int dividend, int divisor) { if (divisor != 0) { return 10 / divisor; } return -1; }
One million executions of the first method take 15 milliseconds, while one million executions of the second method take 5 milliseconds. So you can clearly see that there is a large difference in the execution time when running the code with exceptions and when using a simple conditional to check if the execution should be allowed. Of course, keep in mind that this comparison is very, very simple, and doesn’t reflect real-world scenarios, but it should give you an idea of what to expect when crafting your own code.
The test execution time will differ depending on the machine, but you can run it on your own if you would like by cloning this Github repository.
Troubleshooting Java Exceptions with Sematext
Handling exceptions properly in your Java code is important. Equally important is being able to use them for troubleshooting your Java applications. With Sematext Cloud you can find the most frequent errors and exceptions in your Java application logs, create custom dashboards for your team and set up real-time alerts and anomaly detection to notify you of issues that require your attention. Can you also correlate errors, exceptions, and other application logs with your application performance metrics? You can use Sematext not only for Java monitoring, but also to monitor Elasticsearch, Tomcat, Solr, Kafka, Nginx, your servers, processes, databases, even your packages, and the rest of your infrastructure. With service auto-discovery and monitoring everything is just a click away 🙂
Conclusion
Even though exceptions in Java are not free and have a performance penalty they are invaluable for handling errors in your applications. They help in decoupling the main business code from the error handling code making the whole code cleaner and easier to understand. Using exceptions wisely will make your code look good, be extensible, maintainable, and fun to work with. Use them following exception handling best practices and log everything they tell you into your centralized logging solution so that you can get the most value out of any thrown exception.
If you don’t know where to start looking for the perfect logging solution for you, we wrote in-depth comparisons of various log management tools, log analyzers, and cloud logging services available today to fund the one that fits your use case.
Exceptions and errors both are subclasses of Throwable class. The error indicates a problem that mainly occurs due to the lack of system resources and our application should not catch these types of problems. Some of the examples of errors are system crash error and out of memory error. Errors mostly occur at runtime that’s they belong to an unchecked type.
Exceptions are the problems which can occur at runtime and compile time. It mainly occurs in the code written by the developers. Exceptions are divided into two categories such as checked exceptions and unchecked exceptions.
Sr. No. | Key | Error | Exception |
---|---|---|---|
1 | Type | Classified as an unchecked type | Classified as checked and unchecked |
2 | Package | It belongs to java.lang.error | It belongs to java.lang.Exception |
3 | Recoverable/ Irrecoverable | It is irrecoverable | It is recoverable |
4 | It can’t be occur at compile time | It can occur at run time compile time both | |
5 | Example | OutOfMemoryError ,IOError | NullPointerException , SqlException |
Example of Error
public class ErrorExample { public static void main(String[] args){ recursiveMethod(10) } public static void recursiveMethod(int i){ while(i!=0){ i=i+1; recursiveMethod(i); } } }
Output
Exception in thread "main" java.lang.StackOverflowError at ErrorExample.ErrorExample(Main.java:42)
Example of Exception
public class ExceptionExample { public static void main(String[] args){ int x = 100; int y = 0; int z = x / y; } }
Output
java.lang.ArithmeticException: / by zero at ExceptionExample.main(ExceptionExample.java:7)
Эта статья посвящается очень важному вопросу программирования — исключительным ситуациям и ошибкам (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.
Жду ваших вопросов и комментариев.