Consider this simple program. The program has two files:
File Vehicle.java
class Vehicle {
private int speed = 0;
private int maxSpeed = 100;
public int getSpeed()
{
return speed;
}
public int getMaxSpeed()
{
return maxSpeed;
}
public void speedUp(int increment)
{
if(speed + increment > maxSpeed){
// Throw exception
}else{
speed += increment;
}
}
public void speedDown(int decrement)
{
if(speed - decrement < 0){
// Throw exception
}else{
speed -= decrement;
}
}
}
File HelloWorld.java
public class HelloWorld {
/**
* @param args
*/
public static void main(String[] args) {
Vehicle v1 = new Vehicle();
Vehicle v2 = new Vehicle();
// Do something
// Print something useful, TODO
System.out.println(v1.getSpeed());
}
}
As you can see in the first class, I have added a comment (“// throw exception”) where I would like to throw an exception. Do I have to define my own class for exceptions or is there some general exception class in Java I can use?
asked Aug 4, 2011 at 13:51
Richard KnopRichard Knop
80.3k149 gold badges391 silver badges551 bronze badges
4
You could create your own Exception class:
public class InvalidSpeedException extends Exception {
public InvalidSpeedException(String message){
super(message);
}
}
In your code:
throw new InvalidSpeedException("TOO HIGH");
answered Aug 4, 2011 at 13:55
0
You could use IllegalArgumentException:
public void speedDown(int decrement)
{
if(speed - decrement < 0){
throw new IllegalArgumentException("Final speed can not be less than zero");
}else{
speed -= decrement;
}
}
answered Aug 4, 2011 at 13:53
Maurício LinharesMaurício Linhares
39.8k14 gold badges121 silver badges158 bronze badges
2
Well, there are lots of exceptions to throw, but here is how you throw an exception:
throw new IllegalArgumentException("INVALID");
Also, yes, you can create your own custom exceptions.
A note about exceptions. When you throw an exception (like above) and you catch the exception: the String
that you supply in the exception can be accessed throw the getMessage()
method.
try{
methodThatThrowsException();
}catch(IllegalArgumentException e)
{
e.getMessage();
}
answered Aug 4, 2011 at 13:52
RMTRMT
7,0104 gold badges25 silver badges37 bronze badges
9
It really depends on what you want to do with that exception after you catch it. If you need to differentiate your exception then you have to create your custom Exception
. Otherwise you could just throw new Exception("message goes here");
answered Aug 4, 2011 at 13:54
VladVlad
10.6k2 gold badges34 silver badges38 bronze badges
3
The simplest way to do it would be something like:
throw new java.lang.Exception();
However, the following lines would be unreachable in your code. So, we have two ways:
- Throw a generic exception at the bottom of the method.
- Throw a custom exception in case you don’t want to do 1.
answered Nov 30, 2016 at 11:14
Sudip BhandariSudip Bhandari
2,1251 gold badge27 silver badges25 bronze badges
Java has a large number of built-in exceptions for different scenarios.
In this case, you should throw an IllegalArgumentException
, since the problem is that the caller passed a bad parameter.
answered Aug 4, 2011 at 13:53
SLaksSLaks
863k176 gold badges1900 silver badges1961 bronze badges
1
You can define your own exception class extending java.lang.Exception (that’s for a checked exception – these which must be caught), or extending java.lang.RuntimeException – these exceptions does not have to be caught.
The other solution is to review the Java API and finding an appropriate exception describing your situation: in this particular case I think that the best one would be IllegalArgumentException
.
answered Aug 4, 2011 at 13:55
omnomnomomnomnom
8,8534 gold badges41 silver badges50 bronze badges
It depends. You can throw a more general exception, or a more specific exception. For simpler methods, more general exceptions are enough. If the method is complex, then, throwing a more specific exception will be reliable.
answered Aug 4, 2011 at 13:56
Время на прочтение
7 мин
Количество просмотров 87K
Привет, Хабр! Представляю вашему вниманию перевод статьи Fixing 7 Common Java Exception Handling Mistakes автора Thorben Janssen.
Обработка исключения является одной из наиболее распространенных, но не обязательно одной из самых простых задач. Это все еще одна из часто обсуждаемых тем в опытных командах, и есть несколько передовых методов и распространенных ошибок, о которых вы должны знать.
Вот несколько вещей, которые следует избегать при обработке исключений в вашем приложении.
Ошибка 1: объявление java.lang.Exception или java.lang.Throwable
Как вы уже знаете, вам нужно либо объявить, либо обработать проверяемое исключение. Но проверяемые исключения — это не единственные, которые вы можете указать. Вы можете использовать любой подкласс java.lang.Throwable в предложении throws. Таким образом, вместо указания двух разных исключений, которые выбрасывает следующий фрагмент кода, вы можете просто использовать исключение java.lang.Exception в предложении throws.
public void doNotSpecifyException() throws Exception {
doSomething();
}
public void doSomething() throws NumberFormatException, IllegalArgumentException {
// do something
}
Но это не значит, что вы должны это сделать. Указание Exeption или Throwable делает почти невозможным правильное обращение с ними при вызове вашего метода.Единственная информация, которую получает вызывающий вами метод, заключается в том, что что-то может пойти не так. Но вы не делитесь какой-либо информацией о каких-либо исключительных событиях, которые могут произойти. Вы скрываете эту информацию за обобщенными причинами выброса исключений.Становится еще хуже, когда ваше приложение меняется со временем. Выброс обобщенных исключений скрывает все изменения исключений, которые вызывающий должен ожидать и обрабатывать. Это может привести к нескольким непредвиденным ошибкам, которые необходимо найти в тестовом примере вместо ошибки компилятора.
Используйте конкретные классы
Гораздо лучше указать наиболее конкретные классы исключений, даже если вам приходится использовать несколько из них. Это сообщает вызывающему устройству, какие исключительные событий нужно обрабатывать. Это также позволяет вам обновить предложение throw, когда ваш метод выдает дополнительное исключение. Таким образом, ваши клиенты знают об изменениях и даже получают ошибку, если вы изменяете выбрасываемые исключения. Такое исключение намного проще найти и обработать, чем исключение, которое появляется только при запуске конкретного тестового примера.
public void specifySpecificExceptions() throws NumberFormatException, IllegalArgumentException {
doSomething();
}
Ошибка 2: перехват обобщенных исключений
Серьезность этой ошибки зависит от того, какой программный компонент вы реализуете, и где вы обнаруживаете исключение. Возможно, было бы хорошо поймать java.lang.Exception в основном методе вашего приложения Java SE. Но вы должны предпочесть поймать определенные исключения, если вы реализуете библиотеку или работаете над более глубокими слоями вашего приложения.
Это дает несколько преимуществ. Такой подход позволяет обрабатывать каждый класс исключений по-разному и не позволяет вам перехватывать исключения, которых вы не ожидали.
Но имейте в виду, что первый блок catch, который обрабатывает класс исключения или один из его супер-классов, поймает его. Поэтому сначала обязательно поймайте наиболее специфический класс. В противном случае ваши IDE покажут сообщение об ошибке или предупреждении о недостижимом блоке кода.
try {
doSomething();
} catch (NumberFormatException e) {
// handle the NumberFormatException
log.error(e);
} catch (IllegalArgumentException e) {
// handle the IllegalArgumentException
log.error(e);
}
Ошибка 3: Логирование и проброс исключений
Это одна из самых популярных ошибок при обработке исключений Java. Может показаться логичным регистрировать исключение там, где оно было брошено, а затем пробросить его вызывающему объекту, который может реализовать конкретную обработку для конкретного случая использования. Но вы не должны делать это по трем причинам:
1. У вас недостаточно информации о прецеденте, который хочет реализовать вызывающий объект вашего метода. Исключение может быть частью ожидаемого поведения и обрабатываться клиентом. В этом случае нет необходимости регистрировать его. Это добавит ложное сообщение об ошибке в файл журнала, который должен быть отфильтрован вашей операционной группой.
2. Сообщение журнала не предоставляет никакой информации, которая еще не является частью самого исключения. Его трассировка и трассировка стека должны содержать всю необходимую информацию об исключительном событии. Сообщение описывает это, а трассировка стека содержит подробную информацию о классе, методе и строке, в которой она произошла.
3. Вы можете регистрировать одно и то же исключение несколько раз, когда вы регистрируете его в каждом блоке catch, который его ловит. Это испортит статистику в вашем инструменте мониторинга и затрудняет чтение файла журнала для ваших операций и команды разработчиков.
Регистрируйте исключение там, где вы его обрабатываете
Таким образом, лучше всего регистрировать исключение тогда, когда вы его обрабатываете. Как в следующем фрагменте кода. Метод doSomething генерирует исключение. Метод doMore просто указывает его, потому что у разработчика недостаточно информации для его обработки. Затем он обрабатывается в методе doEvenMore, который также записывает сообщение журнала.
public void doEvenMore() {
try {
doMore();
} catch (NumberFormatException e) {
// handle the NumberFormatException
} catch (IllegalArgumentException e) {
// handle the IllegalArgumentException
}
}
public void doMore() throws NumberFormatException, IllegalArgumentException {
doSomething();
}
public void doSomething() throws NumberFormatException, IllegalArgumentException {
// do something
}
Ошибка 4: использование исключений для управления потоком
Использование исключений для управления потоком вашего приложения считается анти-шаблоном по двум основным причинам:
Они в основном работают как оператор Go To, потому что они отменяют выполнение блока кода и переходят к первому блоку catch, который обрабатывает исключение. Это делает код очень трудным для чтения.
Они не так эффективны, как общие структуры управления Java. Как видно из названия, вы должны использовать их только для исключительных событий, а JVM не оптимизирует их так же, как и другой код.Таким образом, лучше использовать правильные условия, чтобы разбить свои циклы или инструкции if-else, чтобы решить, какие блоки кода должны быть выполнены.
Ошибка 5: удалить причину возникновения исключения
Иногда вам может понадобиться обернуть одно исключение в другое. Возможно, ваша команда решила использовать специальное исключение для бизнеса с кодами ошибок и единой обработкой. Нет ничего плохого в этом подходе, если вы не устраните причину.
Когда вы создаете новое исключение, вы всегда должны устанавливать первоначальное исключение в качестве причины. В противном случае вы потеряете трассировку сообщения и стека, которые описывают исключительное событие, вызвавшее ваше исключение. Класс Exception и все его подклассы предоставляют несколько методов-конструкторов, которые принимают исходное исключение в качестве параметра и задают его как причину.
try {
doSomething();
} catch (NumberFormatException e) {
throw new MyBusinessException(e, ErrorCode.CONFIGURATION_ERROR);
} catch (IllegalArgumentException e) {
throw new MyBusinessException(e, ErrorCode.UNEXPECTED);
}
Ошибка 6: Обобщение исключений
Когда вы обобщаете исключение, вы ловите конкретный, например, NumberFormatException, и вместо этого генерируете неспецифическое java.lang.Exception. Это похоже, но даже хуже, чем первая ошибка, которую я описал в этой статье. Он не только скрывает информацию о конкретном случае ошибки на вашем API, но также затрудняет доступ.
public void doNotGeneralizeException() throws Exception {
try {
doSomething();
} catch (NumberFormatException e) {
throw new Exception(e);
} catch (IllegalArgumentException e) {
throw new Exception(e);
}
}
Как вы можете видеть в следующем фрагменте кода, даже если вы знаете, какие исключения может вызвать метод, вы не можете просто их поймать. Вам нужно поймать общий класс Exception и затем проверить тип его причины. Этот код не только громоздкий для реализации, но его также трудно читать. Становится еще хуже, если вы сочетаете этот подход с ошибкой 5. Это удаляет всю информацию об исключительном событии.
try {
doNotGeneralizeException();
} catch (Exception e) {
if (e.getCause() instanceof NumberFormatException) {
log.error("NumberFormatException: " + e);
} else if (e.getCause() instanceof IllegalArgumentException) {
log.error("IllegalArgumentException: " + e);
} else {
log.error("Unexpected exception: " + e);
}
}
Итак, какой подход лучший?
Будьте конкретны и сохраняйте причину возникновения исключения.
Исключения, которые вы бросаете, должны всегда быть максимально конкретными. И если вы оборачиваете исключение, вы также должны установить исходный исключение в качестве причины, чтобы не потерять трассировку стека и другую информацию, описывающую исключительное событие.
try {
doSomething();
} catch (NumberFormatException e) {
throw new MyBusinessException(e, ErrorCode.CONFIGURATION_ERROR);
} catch (IllegalArgumentException e) {
throw new MyBusinessException(e, ErrorCode.UNEXPECTED);
}
Ошибка 7: добавление ненужных преобразований исключений
Как я уже объяснял ранее, может быть полезно обернуть исключения в пользовательские, если вы установите исходное исключение в качестве причины. Но некоторые архитекторы переусердствуют и вводят специальный класс исключений для каждого архитектурного уровня. Таким образом, они улавливают исключение в уровне персистентности и переносят его в MyPersistenceException. Бизнес-уровень ловит и обертывает его в MyBusinessException, и это продолжается до тех пор, пока оно не достигнет уровня API или не будет обработано.
public void persistCustomer(Customer c) throws MyPersistenceException {
// persist a Customer
}
public void manageCustomer(Customer c) throws MyBusinessException {
// manage a Customer
try {
persistCustomer(c);
} catch (MyPersistenceException e) {
throw new MyBusinessException(e, e.getCode());
}
}
public void createCustomer(Customer c) throws MyApiException {
// create a Customer
try {
manageCustomer(c);
} catch (MyBusinessException e) {
throw new MyApiException(e, e.getCode());
}
}
Легко видеть, что эти дополнительные классы исключений не дают никаких преимуществ. Они просто вводят дополнительные слои, которые оборачивают исключение. И хотя было бы забавно обернуть подарок во множестве красочной бумаги, это не очень хороший подход к разработке программного обеспечения.
Обязательно добавьте информацию
Просто подумайте о коде, который должен обрабатывать исключение или о самом себе, когда вам нужно найти проблему, вызвавшую исключение. Сначала вам нужно прорваться через несколько уровней исключений, чтобы найти исходную причину. И до сегодняшнего дня я никогда не видел приложение, которое использовало этот подход, и добавляло полезную информацию с каждым слоем исключения. Они либо обобщают сообщение об ошибке и код, либо предоставляют избыточную информацию.
Поэтому будьте осторожны с количеством настраиваемых классов исключений, которые вы вводите. Вы всегда должны спрашивать себя, дает ли новый класс исключений дополнительную информацию или другие преимущества. В большинстве случаев для достижения этого вам не требуется более одного уровня пользовательских исключений.
public void persistCustomer(Customer c) {
// persist a Customer
}
public void manageCustomer(Customer c) throws MyBusinessException {
// manage a Customer
throw new MyBusinessException(e, e.getCode());
}
public void createCustomer(Customer c) throws MyBusinessException {
// create a Customer
manageCustomer(c);
}
Одна из неожиданных ошибок — NullPointerException, когда пользователь ничего не делал, а «оно само», при входе на портал онлайн банки использующего TumarCSP.
При этом кнопка Вход не появляется.
Application Error General Exception
Name:cptumar NullPointerException:nullОшибка возникла в ОС Windows 7 x64 и браузере Internet Exlorer 8.
Проблему вызвало обновление антивируса Avast до полной своей версии с подключением расширенных компонентов защиты.
Проблема решается либо отключением экранов антивируса, либо возвратом на предыдущую версию.
Возможно и другие антивирусы тоже могут вызвать появление этой ошибки.
Отключив антивирус, мы видим появление окна входа в систему.
[nx_heading style=»coloredline» heading_tag=»h4″ size=»24″ align=»left»]От автора:[/nx_heading]
Если проблема решена, один из способов сказать «Спасибо» автору — здесь.
Если же проблему разрешить не удалось и появились дополнительные вопросы, задать их можно на нашем форуме, в нашей группе Whatsapp.
Или же, воспользуйтесь нашей услугой «Удаленная помощь» доверив решение проблемы специалисту.
[nx_box title=»Поделиться этой статьей» style=»glass» box_color=»#ED303C»]Если данная статья вам помогла и вы хотели бы в ответ помочь проекту Mhelp.kz, поделитесь этой статьей с другими:
[/nx_box]
Исключение — ошибка, которая нарушает нормальную работу программы. Java обеспечивает надежный объектно-ориентированный способ обработки исключений. Именно его мы и будем изучать в этом руководстве.
Исключение может возникнуть в разного рода ситуациях: неправильные входные данные, аппаратный сбой, сбоя сетевого соединения, ошибка при работе с базой данных и т.д. Именно поэтому любой Java программист должен уметь правильно обрабатывать исключения, понимать причины их появления и следовать лучшим практикам работы с исключениями даже в небольших проектах.
Java — объектно-ориентированный язык программирования, поэтому всякий раз, когда происходит ошибка при выполнении инструкции, создается объект-исключение, а затем нормальный ход выполнения программы останавливается и JRE пытается найти кого-то, кто может справиться (обработать) это исключение. Объект-исключение содержит много информации об отладке, а именно номер строки, где произошло исключение, тип исключения и т.д.
Что и как происходит, когда появляется ошибка
Когда в методе происходит исключение, то процесс создания объекта-исключения и передачи его в Runtime Environment называется «бросать исключение».
После создания исключения, Java Runtime Environment пытается найти обработчик исключения.
Обработчик исключения — блок кода, который может обрабатывать объект-исключение.
Логика нахождения обработчика исключений проста — прежде всего начинается поиск в методе, где возникла ошибка, если соответствующий обработчик не найден, то происходит переход к тому методу, который вызывает этот метод и так далее.
Пример
У нас есть 3 метода, каждый из которых вызывает друг-друга:
А -> В -> С
(А вызывает В, а В вызывает С). Если исключение появляется в методе C, то поиск соответствующего обработчика будет происходить в обратном порядке:С -> В -> А
(сначала там, где было исключение — в С, если там нет обработчика, то идем в метод В — если тут тоже нет, то идем в А).
Если соответствующий обработчик исключений будет найден, то объект-исключение передаётся обработчику.
Обработать исключение — значит «поймать исключение».
Если обработчик исключений не был найден, то программа завершает работу и печатает информации об исключении.
Обратите внимание, что обработка исключений в Java — это фреймворк, который используется только для обработки ошибок времени выполнения. Ошибки компиляции не обрабатываются рамках обработки исключений.
Основные элементы обработки исключений в Java
Мы используем определенные ключевые слова в для создания блока обработки исключений. Давайте рассмотрим их на примере. Также мы напишем простую программу для обработки исключений.
- Бросить исключение (
throw
) — ключевое слово, которое используется для того, чтобы бросить исключение во время выполнения. Мы знаем, что Java Runtime начинает поиск обработчика исключений как только оно будет брошено, но часто нам самим нужно генерировать исключение в нашем коде, например, в программе авторизации, если какое-то полеnull
. Именно для таких случаем и существует возможность бросить исключение. throws
— когда мы бросаем исключение в методе и не обрабатываем его, то мы должны использовать ключевое словоthrows
в сигнатуре метода для того, чтобы пробросить исключение для обработки в другом методе. Вызывающий метод может обработать это исключение или пробросить его еще дальше с помощьюthrows
в сигнатуре метода. Следует отметить, что пробрасывать можно сразу несколько исключений.- Блок
try-catch
используется для обработки исключений в коде. Словоtry
— это начало блока обработки,catch
— конец блока для обработки исключений. Мы можем использовать сразу несколько блоковcatch
при одномtry
.catch
в качестве параметра принимает тип исключения для обработки. finally
— необязательная завершающая конструкция блокаtry-catch
. Как только исключение остановило процесс исполнения программы, вfinally
мы можем безопасно освободить какие-то открытые ресурсы. Следует отметить, чтоfinally
блок выполняется всегда — не смотря на появление исключительной ситуации.
Давайте посмотрим простую программу обработки исключений в Java.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
package ua.com.prologistic; import java.io.FileNotFoundException; import java.io.IOException; public class ExceptionHandling { // в методе main() пробрасывается сразу несколько исключений public static void main(String[] args) throws FileNotFoundException, IOException { // в блоке try-catch перехватываются сразу несколько исключений вызовом дополнительного catch(…) try{ testException(–5); testException(–10); }catch(FileNotFoundException e){ e.printStackTrace(); }catch(IOException e){ e.printStackTrace(); }finally{ System.out.println(“Необязательный блок, но раз уже написан, то выполнятся будет не зависимо от того было исключение или нет”); } testException(15); } // тестовый метод создания, обработки и пробрасывания исключения public static void testException(int i) throws FileNotFoundException, IOException{ if(i < 0){ FileNotFoundException myException = new FileNotFoundException(“число меньше 0: “ + i); throw myException; }else if(i > 10){ throw new IOException(“Число должно быть в пределах от 0 до 10”); } } } |
А в консоле эта программа напишет такое:
java.io.FileNotFoundException: число меньше 0: –5 at ua.com.prologistic.ExceptionHandling.testException(ExceptionHandling.java:24) at ua.com.prologistic.ExceptionHandling.main(ExceptionHandling.java:10) Необязательный блок, но раз уже написан, то выполнятся будет не зависимо от того было исключение или нет Exception in thread “main” java.io.IOException: Число должно быть в пределах от 0 до 10 at ua.com.prologistic.ExceptionHandling.testException(ExceptionHandling.java:27) at ua.com.prologistic.ExceptionHandling.main(ExceptionHandling.java:19) |
Обратите внимание, что метод testException()
бросает исключение, используя ключевое слово throw
, а в сигнатуре метода используется ключевое слово throws
, чтобы дать понять вызывающему методу тип исключений, которые может бросить testException()
.
Важные моменты в обработке исключений:
- Нельзя использовать блоки
catch
илиfinally
без блокаtry
. - Блок
try
также может быть использован только сcatch
блоком, или только сfinally
блоком, или с тем и другим блоком. - Мы можем использовать несколько блоков
catch
только с однимtry
. try-catch
блоки могут быть вложенными — этим они очень похожи наif-else
конструкции.- Мы можем использовать только один, блок
finally
в одномtry-catch
.
Иерархия исключений в Java
Java исключения являются иерархическими, а наследование используется для категоризации различных типов исключений. Throwable
— родительский класс в иерархии Java исключений. Он имеет два дочерних объекта — Error
и Exception
. Исключения далее разделены на проверяемые исключения и исключения времени выполнения.
- Error — это тип ошибок, которые выходят за рамки вашей программы, их невозможно предвидеть или обработать. Это может быть аппаратный сбой, «поломка» JVM или ошибка памяти. Именно для таких необычных ситуаций есть отдельная иерархия ошибок. Мы должны просто знать, что такие ошибки есть и не можем справиться с такими ситуациями. Примеры
Error
:OutOfMemoryError
иStackOverflowError
. - Проверяемые исключения (Checked Exceptions) — тип исключений, которые мы можем предвидеть в программе и попытаться обработать, например,
FileNotFoundException
. Мы должны поймать это исключение и написать внятное и полезное сообщение пользователю о том, что произошло (также желательно логировать ошибки).Exception
— родительский класс всех проверяемых исключений (Checked Exceptions). Если мы бросили проверяемое исключение, то должны поймать его в том же методе или должны пробросить его с помощью ключевого словаthrows
. - Runtime Exception — это ошибки программиста. Например, пытаясь получить элемент из массива, мы должны проверить длину массива, прежде чем пытаться получить элемент — в противном случае это может быть брошен
ArrayIndexOutOfBoundException
.RuntimeException
— родительский класс для всех Runtime исключений. Если мы сами бросаем Runtime Exception в методе, то не обязательно указывать в сигнатуре метода ключевое словоthrows
.
На рисунке 1 представлена иерархия исключений в Java:
Рисунок 1 — Иерархия исключений в Java
Полезные методы в обработке исключений
Класс Exception
и все его подклассы не содержат какие-либо методы для обработки исключений. Все предоставляемые методы находятся в базовом классе Throwable
. Подклассы класса Exception
созданы для того, чтобы определять различные виды исключений. Именно поэтому при обработке исключений мы можем легко определить причину и обработать исключение в соответствии с его типом.
Полезные методы класса Throwable
:
- public String getMessage() — этот метод возвращает сообщение, которое было создано при создании исключения через конструктор.
- public String getLocalizedMessage() — метод, который переопределяют подклассы для локализации конкретное сообщение об исключении. В реализации
Throwable
класса этот метод просто использует методg
etMessage()
, чтобы вернуть сообщение об исключении (Throwable
на вершине иерархии — ему нечего локализировать, поэтому он вызываетgetMessage())
. - public synchronized Throwable getCause() — этот метод возвращает причину исключения или идентификатор в виде
null
, если причина неизвестна. - public String toString() — этот метод возвращает информацию о
Throwable
в форматеString
. - public void printStackTrace() — этот метод выводит информацию трассировки стека в стандартный поток ошибок, этот метод перегружен и мы можем передать
PrintStream
илиPrintWriter
в качестве аргумента, чтобы написать информацию трассировки стека в файл или поток.
Автоматическое управление ресурсами и улучшения блока перехвата ошибок в Java 7
Если вам нужно перехватывать много исключений в одном блоке try-catch
, то блок перехвата будет выглядеть очень некрасиво и в основном будет состоять из избыточного кода. Именно поэтому в Java 7 это было значительно улучшено и теперь мы можем перехватывать несколько исключений в одном блоке catch
.
Это выглядит следующим образом:
catch(IOException | SQLException | Exception ex){ //что-то сделать с перехваченной ошибкой… } |
Как видим, здесь блок catch
перехватывает сразу несколько исключений — это очень красиво, компактно и удобно.
В большинстве случаев мы используем блок finally
для того, чтобы закрыть открытые потоки, подключения или освободить другие ресурсы. Очень часто мы забываем закрыть и получаем runtime исключения. Такие исключения трудно отлаживать. Поэтому в Java 7 был введен try
с ресурсами, где мы можем открыть ресурс в самом try
и использовать его внутри блока try-catch
. Когда программа заканчивает выполнение блока try-catch
, то среда выполнения автоматически закрывает эти ресурсы. Вот пример try-catch
блока с ресурсами:
// try c ресурсами try (MyResource mr = new MyResource()) { System.out.println(“Красивый и компактный код в try c ресурсами”); } catch (Exception e) { e.printStackTrace(); } |
Создание своих классов исключений
Java предоставляет много классов исключений, но иногда нам может понадобиться создать свои «кастомные» классы исключений. Это может понадобиться для того, чтобы уведомить абонента о конкретном типе исключения с соответствующим сообщением. Например, мы напишем метод для обработки только текстовых файлов, поэтому мы можем написать свой класс исключений и передавать соответствующий код ошибки, когда кто-то передает неподходящий тип файла в качестве входных данных.
Вот пример своего класса исключений и его использование:
package ua.com.prologistic; // наследуемся от класс Exception public class MyException extends Exception { private String errorCode = “Unknown_Exception”; public MyException(String message, String errorCode){ super(message); this.errorCode = errorCode; } public String getErrorCode(){ return this.errorCode; } } |
А теперь проверим в работе наш класс MyException:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
package ua.com.prologistic; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; public class CustomExceptionExample { public static void main(String[] args) throws MyException { try { processFile(“file.txt”); } catch (MyException e) { processErrorCodes(e); } } // метод для обработки ошибок private static void processErrorCodes(MyException e) throws MyException { // здесь мы ищем указанный при выбросе исключения код ошибки и сообщаем пользователю что произошло switch(e.getErrorCode()){ case “BAD_FILE_TYPE”: System.out.println(“Неподходящий тип файла”); throw e; case “FILE_NOT_FOUND_EXCEPTION”: System.out.println(“Файл не найден”); throw e; case “FILE_CLOSE_EXCEPTION”: System.out.println(“Ошибка при закрытии файла”); break; default: System.out.println(“Произошла неизвестная ошибка “ + e.getMessage()); e.printStackTrace(); } } // метод для работы с файлом, который пробрасывает наш тип исключений private static void processFile(String file) throws MyException { InputStream fis = null; try { fis = new FileInputStream(file); } catch (FileNotFoundException e) { // здесь мы бросаем исключение с указанием кода ошибки throw new MyException(e.getMessage(),“FILE_NOT_FOUND_EXCEPTION”); }finally{ try { if(fis !=null)fis.close(); } catch (IOException e) { // здесь мы бросаем исключение с указанием кода ошибки throw new MyException(e.getMessage(),“FILE_CLOSE_EXCEPTION”); } } } } |
Полезные советы по обработке исключений в Java
- Не используйте для перехвата исключений класс
Exception
. В иерархии исключений есть множество классов на все случаи жизни вашей программы, которые не только эффективно обработают конкретную ошибку, но и предоставят полезную для пользователя и отладки информацию. - Бросайте исключение как можно раньше. Это является хорошей практикой программирования на Java.
- Ловите исключения только тогда, когда сможете эффективно для пользователя и отладки их обработать.
- Освобождайте ресурсы. Перехватывая исключение всегда закрывайте открытые ресурсы. Еще проще и эффективнее это делать с
Java 7
. Используйте try с ресурсами для лаконичного и красивого кода. - Логируйте исключения. Логируйте сообщения, которые предоставляет исключение. В большинстве случаев это даст вам четкое понимание причин и поможет в отладке. Не оставляйте пустым блок
catch
, иначе он будет просто поглощать исключение без каких-либо значимых деталей для отладки. - Один catch для нескольких исключений. Используйте преимущества Java 7 для удобства и красоты вашего кода.
- Используйте свои исключения. Это позволит вам лучше чувствовать свою программу и эффективнее с ней работать.
- Соглашения об именовании. Когда вы создать свои классы исключений, следите за тем, что из самого названия класса будет ясно, что это исключение.
- Используйте исключения с умом. Бросить исключение — достаточно дорогостоящая в Java операция. Возможно, в некоторых случаях будем уместно не бросать исключений, а вернуть, например, логическую переменную, которая обозначала успешное или не успешное выполнение метода.
- Документируйте исключения. Желательно писать javadoc @throws для ваших исключений. Это будет особенно полезно в тех случаях, когда ваша программа предоставляет интерфейс для работы с другими приложениями.
Вот и все, что нужно знать об обработке исключений в Java.
Обзор
Обработка исключений в Java – одна из самых основных и фундаментальных
вещей, которую разработчик должен знать наизусть. К сожалению, это часто
упускается из виду, и важность обработки исключений недооценивается –
она так же важна, как и остальной код.
В этой статье давайте рассмотрим все, что вам нужно знать об обработке
исключений в Java, а также о хороших и плохих практиках.
Что такое обработка исключений?
В реальной жизни мы ежедневно окружены обработкой исключений.
При заказе товара в интернет-магазине – товар может отсутствовать на
складе или может произойти сбой в доставке. Таким исключительным
условиям можно противодействовать, производя другой продукт или
отправляя новый после неудачной доставки.
При создании приложений они могут столкнуться со всевозможными
исключительными условиями. К счастью, будучи опытным в обработке
исключений, таким условиям можно противодействовать, изменяя поток кода.
Зачем использовать обработку исключений?
При создании приложений мы обычно работаем в идеальной среде – файловая
система может предоставить нам все файлы, которые мы запрашиваем, наше
интернет-соединение стабильно, а JVM всегда может предоставить
достаточно памяти для наших нужд.
К сожалению, на самом деле среда далека от идеала – файл не может быть
найден, подключение к Интернету время от времени прерывается, JVM не
может предоставить достаточно памяти, и мы остаемся с пугающей
StackOverflowError
.
Если мы не справимся с такими условиями, все приложение окажется в
руинах, а весь остальной код устареет. Следовательно, мы должны
уметь писать код, который может адаптироваться к таким ситуациям.
Представьте, что компания не может решить простую проблему, возникшую
после заказа продукта – вы не хотите, чтобы ваше приложение работало
таким образом.
Иерархия исключений
Все это вызывает вопрос – что это за исключения в глазах Java и JVM?
В конце концов, исключения – это просто объекты Java, расширяющие
интерфейс Throwable
---> Throwable <---
| (checked) |
| |
| |
---> Exception Error
| (checked) (unchecked)
|
RuntimeException
(unchecked)
Когда мы говорим об исключительных условиях, мы обычно имеем в виду одно
из трех:
- Проверенные исключения
- Непроверенные исключения / исключения времени выполнения
- Ошибки
Примечание . Термины «время выполнения» и «непроверенный» часто
используются как синонимы и относятся к одному и тому же типу
исключений.
Проверенные исключения
Отмеченные исключения – это исключения, которые мы обычно можем
предвидеть и заранее планировать в нашем приложении. Это также
исключения, которые компилятор Java требует от нас либо обработать,
либо объявить при написании кода.
Правило дескриптора или объявления относится к нашей ответственности:
либо объявить, что метод генерирует исключение в стеке вызовов, не
предпринимая особых усилий для его предотвращения, либо обработать
исключение нашим собственным кодом, что обычно приводит к восстановлению
программы из исключительное условие.
Это причина, по которой они называются проверенными исключениями .
Компилятор может обнаружить их до выполнения, и вы знаете об их
потенциальном существовании при написании кода.
Непроверенные исключения
Непроверенные исключения – это исключения, которые обычно возникают по
вине человека, а не окружающей среды. Эти исключения проверяются не во
время компиляции, а во время выполнения, поэтому они также называются
исключениями времени выполнения .
Им часто можно противодействовать, реализуя простые проверки перед
сегментом кода, который потенциально может быть использован таким
образом, чтобы сформировать исключение во время выполнения, но об этом
позже.
Ошибки
Ошибки – это самые серьезные исключительные условия, с которыми вы
можете столкнуться. Их часто невозможно восстановить, и с ними
невозможно справиться. Единственное, что мы, как разработчики, можем
сделать – это оптимизировать код в надежде, что ошибки никогда не
возникнут.
Ошибки могут возникать по вине человека или окружающей среды. Создание
бесконечно повторяющегося метода может привести к StackOverflowError
,
или утечка памяти может привести к OutOfMemoryError
.
Как обрабатывать исключения
бросает и бросает
Самый простой способ исправить ошибку компилятора при работе с
проверенным исключением – просто выбросить ее.
public File getFile(String url) throws FileNotFoundException {
// some code
throw new FileNotFoundException();
}
Нам необходимо пометить подпись нашего метода предложением throws
.
Метод может добавить столько исключений, сколько необходимо в своем
throws
, и может выбросить их позже в коде, но не обязательно. Этот
метод не требует оператора return
, даже если он определяет тип
возврата. Это связано с тем, что по умолчанию генерируется исключение,
которое резко завершает выполнение метода. Следовательно, return
будет
недоступен и вызовет ошибку компиляции.
Имейте в виду, что любой, кто вызывает этот метод, также должен
следовать правилу дескриптора или объявления.
При создании исключения мы можем либо создать новое исключение, как в
предыдущем примере, либо пойманное исключение.
блоки try-catch
Более распространенным подходом было бы использование try
catch
для
перехвата и обработки возникающего исключения:
public String readFirstLine(String url) throws FileNotFoundException {
try {
Scanner scanner = new Scanner(new File(url));
return scanner.nextLine();
} catch(FileNotFoundException ex) {
throw ex;
}
}
В этом примере мы «пометили» рискованный сегмент кода, заключив его в
блок try
Это сообщает компилятору, что мы знаем о потенциальном
исключении и собираемся обработать его, если оно возникнет.
Этот код пытается прочитать содержимое файла, и если файл не найден,
FileNotFoundException
пойман и вызван повторно. Подробнее об этом
позже.
Запуск этого фрагмента кода без действительного URL-адреса приведет к
сбору исключения:
Exception in thread "main" java.io.FileNotFoundException: some_file (The system cannot find the file specified) <-- some_file doesn't exist
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.util.Scanner.<init>(Scanner.java:611)
at Exceptions.ExceptionHandling.readFirstLine(ExceptionHandling.java:15) <-- Exception arises on the the readFirstLine() method, on line 15
at Exceptions.ExceptionHandling.main(ExceptionHandling.java:10) <-- readFirstLine() is called by main() on line 10
...
В качестве альтернативы, мы можем попытаться вылечиться из этого
состояния вместо того, чтобы повторно бросить:
public static String readFirstLine(String url) {
try {
Scanner scanner = new Scanner(new File(url));
return scanner.nextLine();
} catch(FileNotFoundException ex) {
System.out.println("File not found.");
return null;
}
}
Запуск этого фрагмента кода без действительного URL-адреса приведет к:
File not found.
наконец блоки
Представляя новый тип блока, блок finally
выполняется независимо от
того, что происходит в блоке try. Даже если он внезапно завершится
выдачей исключения, finally
будет выполнен.
Это часто использовалось для закрытия ресурсов, которые были открыты в
try
поскольку возникающее исключение пропускало код, закрывающий их:
public String readFirstLine(String path) throws IOException {
BufferedReader br = new BufferedReader(new FileReader(path));
try {
return br.readLine();
} finally {
if(br != null) br.close();
}
}
Однако этот подход не одобрялся после выпуска Java 7, в котором был
представлен лучший и более чистый способ закрытия ресурсов, и в
настоящее время считается плохой практикой.
Заявление о попытках с ресурсами
Ранее сложный и подробный блок можно заменить на:
static String readFirstLineFromFile(String path) throws IOException {
try(BufferedReader br = new BufferedReader(new FileReader(path))) {
return br.readLine();
}
}
Он намного чище и, очевидно, упрощен за счет включения объявления в
круглые скобки блока try
Кроме того, вы можете включить несколько ресурсов в этот блок, один за
другим:
static String multipleResources(String path) throws IOException {
try(BufferedReader br = new BufferedReader(new FileReader(path));
BufferedWriter writer = new BufferedWriter(path, charset)) {
// some code
}
}
Таким образом, вам не нужно беспокоиться о закрытии ресурсов
самостоятельно, поскольку блок try-with-resources гарантирует, что
ресурсы будут закрыты по окончании инструкции.
Множественные блоки захвата
Когда код, который мы пишем, может генерировать более одного исключения,
мы можем использовать несколько блоков catch для их индивидуальной
обработки:
public void parseFile(String filePath) {
try {
// some code
} catch (IOException ex) {
// handle
} catch (NumberFormatException ex) {
// handle
}
}
Когда try
вызывает исключение, JVM проверяет, является ли первое
пойманное исключение подходящим, и, если нет, продолжает, пока не найдет
его.
Примечание . При перехвате общего исключения будут перехвачены все
его подклассы, поэтому их не требуется перехватывать по отдельности.
Поймав FileNotFound
исключение не является необходимым в этом примере,
потому что она простирается от IOException
, но если возникнет
необходимость, мы можем поймать его до IOException
:
public void parseFile(String filePath) {
try {
// some code
} catch(FileNotFoundException ex) {
// handle
} catch (IOException ex) {
// handle
} catch (NumberFormatException ex) {
// handle
}
}
Таким образом, мы можем обрабатывать более конкретное исключение иначе,
чем более общее.
Примечание . При перехвате нескольких исключений компилятор Java
требует от нас поместить более конкретные исключения перед более общими,
иначе они будут недоступны и приведут к ошибке компилятора.
Блоки улова Союза
Чтобы уменьшить шаблонный код, в Java 7 также были введены блоки
перехвата объединения . Они позволяют нам обрабатывать несколько
исключений одинаково и обрабатывать их исключения в одном блоке:
public void parseFile(String filePath) {
try {
// some code
} catch (IOException | NumberFormatException ex) {
// handle
}
}
Как выбросить исключения
Иногда мы не хотим обрабатывать исключения. В таких случаях мы должны
заботиться о том, чтобы генерировать их только тогда, когда это
необходимо, и позволять кому-то другому, вызывая наш метод, обрабатывать
их соответствующим образом.
Выброс проверенного исключения
Когда что – то идет не так, как количество пользователей в настоящее
время подключения к нашему сервису превышает сумму максимальной для
сервера , чтобы справиться легко, мы хотим , чтобы throw
исключение ,
чтобы указать на исключительную ситуацию:
public void countUsers() throws TooManyUsersException {
int numberOfUsers = 0;
while(numberOfUsers < 500) {
// some code
numberOfUsers++;
}
throw new TooManyUsersException("The number of users exceeds our maximum
recommended amount.");
}
}
Этот код увеличит numberOfUsers
до тех пор, пока не превысит
максимальное рекомендуемое количество, после чего вызовет исключение.
Поскольку это проверенное исключение, мы должны добавить предложение
throws
в сигнатуру метода.
Определить подобное исключение так же просто, как написать следующее:
public class TooManyUsersException extends Exception {
public TooManyUsersException(String message) {
super(message);
}
}
Выдача непроверенного исключения
Выдача исключений времени выполнения обычно сводится к проверке ввода,
поскольку они чаще всего возникают из-за ошибочного ввода – либо в форме
IllegalArgumentException
, NumberFormatException
,
ArrayIndexOutOfBoundsException
, либо NullPointerException
:
public void authenticateUser(String username) throws UserNotAuthenticatedException {
if(!isAuthenticated(username)) {
throw new UserNotAuthenticatedException("User is not authenticated!");
}
}
Поскольку мы генерируем исключение времени выполнения, нет необходимости
включать его в сигнатуру метода, как в приведенном выше примере, но это
часто считается хорошей практикой, по крайней мере, для документации.
Опять же, определить настраиваемое исключение времени выполнения,
подобное этому, так же просто, как:
public class UserNotAuthenticatedException extends RuntimeException {
public UserNotAuthenticatedException(String message) {
super(message);
}
}
Перекрашивание
Ранее уже упоминалось о повторном генерировании исключения, поэтому вот
небольшой раздел для пояснения:
public String readFirstLine(String url) throws FileNotFoundException {
try {
Scanner scanner = new Scanner(new File(url));
return scanner.nextLine();
} catch(FileNotFoundException ex) {
throw ex;
}
}
Повторное отображение относится к процессу создания уже перехваченного
исключения, а не к созданию нового.
Упаковка
С другой стороны, обертывание относится к процессу обертывания уже
пойманного исключения в другое исключение:
public String readFirstLine(String url) throws FileNotFoundException {
try {
Scanner scanner = new Scanner(new File(url));
return scanner.nextLine();
} catch(FileNotFoundException ex) {
throw new SomeOtherException(ex);
}
}
Повторное отображение Throwable или _Exception *?
Эти классы верхнего уровня можно поймать и повторно выбросить, но
способы этого могут варьироваться:
public void parseFile(String filePath) {
try {
throw new NumberFormatException();
} catch (Throwable t) {
throw t;
}
}
В этом случае метод NumberFormatException
которое является исключением
во время выполнения. Из-за этого нам не нужно отмечать сигнатуру метода
с помощью NumberFormatException
или Throwable
.
Однако, если мы выбросим проверенное исключение в методе:
public void parseFile(String filePath) throws Throwable {
try {
throw new IOException();
} catch (Throwable t) {
throw t;
}
}
Теперь мы должны объявить, что метод выбрасывает Throwable
. Почему
это может быть полезно – это обширная тема, выходящая за рамки этого
блога, но в этом конкретном случае есть способы использования.
Наследование исключений
Подклассы, наследующие метод, могут генерировать меньше проверенных
исключений, чем их суперкласс:
public class SomeClass {
public void doSomething() throws SomeException {
// some code
}
}
С этим определением следующий метод вызовет ошибку компилятора:
public class OtherClass extends SomeClass {
@Override
public void doSomething() throws OtherException {
// some code
}
}
Лучшие и худшие методы обработки исключений
Со всем этим вы должны быть хорошо знакомы с тем, как работают
исключения и как их использовать. Теперь давайте рассмотрим лучшие и
худшие практики обработки исключений, которые мы, надеюсь, теперь
полностью понимаем.
Лучшие практики обработки исключений
Избегайте исключительных условий
Иногда с помощью простых проверок можно вообще избежать формирования
исключения:
public Employee getEmployee(int i) {
Employee[] employeeArray = {new Employee("David"), new Employee("Rhett"), new
Employee("Scott")};
if(i >= employeeArray.length) {
System.out.println("Index is too high!");
return null;
} else {
System.out.println("Employee found: " + employeeArray[i].name);
return employeeArray[i];
}
}
}
Вызов этого метода с допустимым индексом приведет к:
Employee found: Scott
Но вызов этого метода с индексом, выходящим за границы, приведет к:
Index is too high!
В любом случае, даже если индекс слишком высок, вызывающая нарушение
строка кода не будет выполняться и никаких исключений не возникнет.
Используйте try-with-resources
Как уже упоминалось выше, всегда лучше использовать более новый, более
лаконичный и понятный подход при работе с ресурсами.
Закройте ресурсы в try-catch-finally
Если вы по какой-либо причине не используете предыдущий совет, по
крайней мере, не забудьте закрыть ресурсы вручную в блоке finally.
Я не буду включать пример кода для этого, поскольку оба уже были
предоставлены для краткости.
Наихудшие методы обработки исключений
Проглатывание исключений
Если ваше намерение состоит в том, чтобы просто удовлетворить
компилятор, вы можете легко сделать это, проглотив исключение :
public void parseFile(String filePath) {
try {
// some code that forms an exception
} catch (Exception ex) {}
}
Проглатывание исключения означает перехват исключения, а не
устранение проблемы.
Таким образом, компилятор удовлетворен, поскольку исключение
перехвачено, но вся важная полезная информация, которую мы могли извлечь
из исключения для отладки, потеряна, и мы ничего не сделали для
восстановления из этого исключительного состояния.
Еще одна очень распространенная практика – просто распечатать
трассировку стека исключения:
public void parseFile(String filePath) {
try {
// some code that forms an exception
} catch(Exception ex) {
ex.printStackTrace();
}
}
Такой подход создает иллюзию управляемости. Да, хотя это лучше, чем
просто игнорировать исключение, распечатав соответствующую информацию,
это не обрабатывает исключительное условие больше, чем его
игнорирование.
Вернуться в окончательный блок
Согласно JLS ( спецификация языка
Java
):
Если выполнение блока try завершается внезапно по какой-либо другой
причине R, то выполняетсяfinally
, а затем есть выбор.
Итак, в терминологии документации, если finally
завершается нормально,
то try
завершается внезапно по причине R.
Если finally
завершается внезапно по причине S, то try
завершается
внезапно по причине S (и причина R отбрасывается).
По сути, внезапно вернувшись из finally
, JVM отбросит исключение из
try
и все ценные данные из него будут потеряны:
public String doSomething() {
String name = "David";
try {
throw new IOException();
} finally {
return name;
}
}
В этом случае, даже несмотря на то, что try
генерирует новое
IOException
, мы используем return
в finally
, внезапно завершая
его. Это приводит try
из-за оператора return, а не IOException
, по
существу отбрасывая исключение в процессе.
Вбрасывание окончательного блока
Очень похоже на предыдущий пример, использование throw
в finally
приведет к удалению исключения из блока try-catch:
public static String doSomething() {
try {
// some code that forms an exception
} catch(IOException io) {
throw io;
} finally {
throw new MyException();
}
}
В этом примере MyException
, созданное внутри finally
, затмевает
исключение, созданное catch
и вся ценная информация будет отброшена.
Имитация оператора goto
Критическое мышление и творческие способы найти решение проблемы –
хорошая черта, но некоторые решения, какими бы креативными они ни были,
неэффективны и излишни.
В Java нет оператора goto, как в некоторых других языках, но вместо
него используются метки для перехода по коду:
public void jumpForward() {
label: {
someMethod();
if (condition) break label;
otherMethod();
}
}
Тем не менее, некоторые люди все еще используют исключения для их
имитации:
public void jumpForward() {
try {
// some code 1
throw new MyException();
// some code 2
} catch(MyException ex) {
// some code 3
}
}
Использование исключений для этой цели неэффективно и медленно.
Исключения предназначены для исключительного кода и должны
использоваться для исключительного кода.
Регистрация и метание
При попытке отладки фрагмента кода и выяснения того, что происходит, не
регистрируйте и не генерируйте исключение одновременно:
public static String readFirstLine(String url) throws FileNotFoundException {
try {
Scanner scanner = new Scanner(new File(url));
return scanner.nextLine();
} catch(FileNotFoundException ex) {
LOGGER.error("FileNotFoundException: ", ex);
throw ex;
}
}
Это избыточно и просто приведет к появлению кучи сообщений журнала,
которые на самом деле не нужны. Объем текста уменьшит видимость
журналов.
Перехват исключения или бросок
Почему бы нам просто не поймать Exception или Throwable, если он
улавливает все подклассы?
Обычно не рекомендуется делать это, если нет веской конкретной причины
поймать любой из этих двух.
Catching Exception
будет перехватывать как проверенные исключения, так
и исключения времени выполнения. Исключения времени выполнения
представляют собой проблемы, которые являются прямым результатом
проблемы программирования, и поэтому их не следует обнаруживать,
поскольку нельзя разумно ожидать их восстановления или обработки.
Catching Throwable
поймает все . Это включает в себя все ошибки,
которые на самом деле никоим образом не предназначены для обнаружения.
Заключение
В этой статье мы рассмотрели исключения и их обработку с нуля. После
этого мы рассмотрели лучшие и худшие практики обработки исключений в
Java.
Надеюсь, вы нашли этот блог информативным и образовательным, удачного
кодирования!