-
Notifications
You must be signed in to change notification settings - Fork 3
Exceptions
###Введение Часто на собеседованиях спрашивают про Exceptions и их обработку. Поэтому решил написать про это.
Очень распространенная штука это - Exceptions, но что это такое?
На пальцах - Exception это сигнал о нестандартной ситуации.
Например - мы не можем закрыть файл или удалить что-то, не хватает памяти и т.д
Ниже мы видим иерархию исключений, корень - java.lang.Throwable
и два класса - java.lang.Exception
и java.lang.Error
Ну а java.lang.Exception
это super class для java.lang.RuntimeException
java.lang.Throwable
и java.lang.Exception
это так называемые checked exceptions(Проверяемые), java.lang.RuntimeException
и java.lang.Error
это unchecked.
Checked exceptions проверяются компилятором в compile time. И их мы обязаны обработать. В то время как RE(Runtime Exceptions) мы не обязаны явно перехватывать и обрабатывать, ну на то они и runtime:)
Мы также можем перехватывать исключения. И перехватывать мы можем все виды исключений.
Но тогда зачем нам столько видов исключений? Давайте подумаем - какая разница между Error и Exception?
Error - это более серьезная ситуация, нежели Exception. Т.е если происходит что-то такое, что мы либо не можем исправить, либо это крайне сложно починить, например, у нас закончилась память или мы вызываем несуществующий метод - это вот Error
. Т.е непросто исключительная ситуация - а прямо рядом с паникой:)
Но если мы не можем закрыть файл, мы делаем какой-нибудь запрос, не можем строку к число преобразовать - это ситуации, после которых мы можем продолжить работать, мы можем перехватить это, обработать(например - попросить пользователя ввести еще раз число) - это java.lang.Exception
.
Т.е разница - в логическом разделении.
Теперь посмотрим глубже на Exceptions - java.lang.Exception
and java.lang.RuntimeException
. RuntimeException - это исключения падающие в Runtime, т.е когда мы уже работаем с нашим приложением.
Надо понимать, что Exceprions - это class, т.е мы туда можем положить свою логику, методы и переменные, если надо. Однако кидать исключения - довольно дорогая операция, поэтому не надо просто так разбрасываться ими.
Когда что кидать?
-
java.lang.Exception Это ситуации, которые нам не подконтрольны, т.е не смогли закрыть файл, не смогли дессериализовать класс - мы ничего не можем сделать, но можем отреагировать. Опять же мы обязаны такие исключения обрабатывать. Либо прокидывать вверх с помощью throws, в сигнатуру метода. Метод может декларировать сколько угодно исключений.
-
java.lang.RuntimeException А вот тут уже - это наши ошибки, ошибки разработчика скорее. Т.е обращаемся к null, делим на ноль где-то и прочее. Мы можем эти ошибки перехватывать, а можем пропускать. Ловить такие ошибки постоянно - не совсем правильно наверное, так как мы можем так и не понять причину ошибок и падений.
-
java.lang.Error Критические ошибки, после которых мы не можем или с трудом можем продолжать работу. Как и все остальные - мы можем такие ошибки ловить, но зачем? Ловить их можно только в случае, если мы можем или знаем как нам поступить в таких ситуациях.
Кидать мы можем тоже любые исключения.
Ловить и обрабатывать ошибки - try/catch/finally.
Расположение catch blocks - важно. Пусть у нас метод бросает IOException и Exception, мы пишем что-то типа такого:
try {
method();
} catch (Exception e) {//do some logic 1}
catch (IOException e) {//do some logic 2}
И видим, что IOException обработка недостижима, мы более широкий фильтр "отлова" установили выше.
Это похоже на то, как мы используем сито. Есть более широкие, есть уже. Так вот, если установить выше всех узкое сито - до низу почти ничего не дойдет, ведь мы все перехватим выше. Принцип ясен.
Отсюда понятно, что если нам надо перехватить прямо вот все, то надо ловить Throwable:
try {
method();
} catch (Throwable t) {//do some logic 1}
Отметим, что в таком подходе мы ловим и RE, и Error!
Ловим RE:
try {
String numberAsString = "one";
Double res = Double.valueOf(numberAsString);
} catch (RuntimeException re) {
System.out.println("Error while convert string to double!");
}
Ловим Error:
try {
throw new Error();
} catch (RuntimeException re) {
System.out.println("RE");
} catch (Error error) {
System.out.println("ERROR");
}
}
Все вроде бы ясно и понятно. Теперь вот еще что.
Полезно иногда иметь свою иерархию Exceptions. Например, у нас есть method2() и он кидает целых 3 разных Exception-а. Имея свою иерархию мы просто пишем 3 catch block-а на каждое исключение и обрабатываем как нам надо. Без этого у нас был бы только один catch block с перехватом Exception, где мы уже будем понимать что мы вообще перехватили и как это обрабатывать.
Идем дальше.
А что если наше исключение прерывает поток(interrupt thread) - все просто используем Thread.UncaughtExceptionHandler
.
И еще раз! Аккуратнее с обработкой исключений.
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"!
####Вывод
- Используем обрабатываемые исключения в случае, когда мы понимаем, что тут может быть ошибка.
- Не обрабатываемое исключение - если это наша ошибка.
- Полезно иметь свою иерархию исключений.
- Используйте finally, если работаете с ресурсами и try-with-resources
- Кидаем исключения с помощью
throw
- Поднять исключение в метод -
throws
- Все проверяемые исключения обязаны быть отловлены!