1. Обзор
В этом руководстве мы рассмотрим ключевое слово finally
в Java. Мы увидим, как использовать его вместе с блоками try/catch
при обработке ошибок. Хотя finally
предназначен для гарантии выполнения кода, мы обсудим исключительные ситуации, в которых JVM его не выполняет.
Мы также обсудим некоторые распространенные ловушки, когда блок finally
может привести к неожиданному результату.
2. Что , наконец?
наконец
, определяет блок кода, который мы используем вместе с ключевым словом try
. Он определяет код, который всегда запускается после блока try
и любого блока catch
до завершения метода.
Блок finally
выполняется независимо от того, выброшено или перехвачено исключение .
2.1. Быстрый пример
Давайте посмотрим на finally
в блоке try-catch-finally
:
try {
System.out.println("The count is " + Integer.parseInt(count));
} catch (NumberFormatException e) {
System.out.println("No count");
} finally {
System.out.println("In finally");
}
В этом примере, независимо от значения параметра count
, JVM выполняет блок finally и печатает
«In finally»
.
2.2. Использование finally
без блока catch
Кроме того, мы можем использовать блок finally
с блоком try
независимо от того, присутствует ли блок catch
:
try {
System.out.println("Inside try");
} finally {
System.out.println("Inside finally");
}
И мы получим вывод:
Inside try
Inside finally
2.3. Почему , наконец,
полезно
Обычно мы используем блок finally
для выполнения кода очистки, такого как закрытие соединений, закрытие файлов или освобождение потоков, поскольку он выполняется независимо от исключения.
Примечание: try-with-resources также можно использовать для закрытия ресурсов вместо блока finally
.
3. Когда , наконец,
выполняется
Давайте посмотрим на все перестановки, когда JVM выполняет блоки finally
, чтобы мы могли лучше понять это.
3.1. Исключение не выбрасывается
Когда блок try
завершается, блок finally
выполняется, даже если исключения не было:
try {
System.out.println("Inside try");
} finally {
System.out.println("Inside finally");
}
В этом примере мы не выбрасываем исключение из блока try .
Таким образом, JVM выполняет весь код как в блоках try
, так и в блоках finally .
Это выводит:
Inside try
Inside finally
3.2. Исключение выброшено и не обрабатывается
Если есть исключение и оно не перехвачено, блок finally
все равно выполняется:
try {
System.out.println("Inside try");
throw new Exception();
} finally {
System.out.println("Inside finally");
}
JVM выполняет блок finally
даже в случае необработанного исключения.
И вывод будет:
Inside try
Inside finally
Exception in thread "main" java.lang.Exception
3.3. Исключение выбрасывается и обрабатывается
Если есть исключение, и оно перехватывается блоком catch
, блок finally
все равно выполняется:
try {
System.out.println("Inside try");
throw new Exception();
} catch (Exception e) {
System.out.println("Inside catch");
} finally {
System.out.println("Inside finally");
}
В этом случае блок catch
обрабатывает выброшенное исключение, а затем JVM выполняет блок finally
и выдает результат:
Inside try
Inside catch
Inside finally
3.4. Метод возвращает из блока try
Даже возврат из метода не предотвратит запуск блоков finally :
try {
System.out.println("Inside try");
return "from try";
} finally {
System.out.println("Inside finally");
}
Здесь, несмотря на то, что метод имеет оператор return
, JVM выполняет блок finally
, прежде чем передать управление вызывающему методу.
Мы получим вывод:
Inside try
Inside finally
3.5. Метод возвращает из блока catch
Когда блок catch
содержит оператор return
, блок finally
все равно вызывается:
try {
System.out.println("Inside try");
throw new Exception();
} catch (Exception e) {
System.out.println("Inside catch");
return "from catch";
} finally {
System.out.println("Inside finally");
}
Когда мы выбрасываем исключение из блока try
, блок catch
обрабатывает исключение. Хотя в блоке catch
есть оператор return , JVM выполняет блок finally
перед передачей управления вызывающему методу и выводит:
Inside try
Inside catch
Inside finally
4. Когда наконец
не выполняется
Хотя мы всегда ожидаем, что JVM выполнит операторы внутри блока finally
, бывают ситуации, когда JVM не будет выполнять блок finally
.
Мы уже могли ожидать, что если операционная система остановит нашу программу, то программа не получит возможности выполнить весь свой код. Есть также некоторые действия, которые мы можем предпринять, чтобы предотвратить выполнение отложенного блока finally .
4.1. Вызов System.exit
В этом случае мы завершаем JVM, вызывая System.exit
, и, следовательно, JVM не будет выполнять наш блок finally :
try {
System.out.println("Inside try");
System.exit(1);
} finally {
System.out.println("Inside finally");
}
Это выводит:
Inside try
4.2. Вызов остановки
Подобно System.exit
, вызов Runtime.halt
также останавливает выполнение, и JVM не выполняет никаких блоков finally :
try {
System.out.println("Inside try");
Runtime.getRuntime().halt(1);
} finally {
System.out.println("Inside finally");
}
Таким образом, на выходе будет:
Inside try
4.3. Демоническая нить
Если поток демона начинает выполнение блока try/finally
, а все остальные потоки, не являющиеся демонами, завершаются до того, как поток демона выполнит блок finally
, JVM не ждет, пока поток демона завершит выполнение блока finally
:
Runnable runnable = () -> {
try {
System.out.println("Inside try");
} finally {
try {
Thread.sleep(1000);
System.out.println("Inside finally");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
Thread regular = new Thread(runnable);
Thread daemon = new Thread(runnable);
daemon.setDaemon(true);
regular.start();
Thread.sleep(300);
daemon.start();
В этом примере runnable
печатает «Inside try»
, как только входит в метод, и ждет 1 секунду, прежде чем напечатать «Inside finally»
.
Здесь мы запускаем обычный
поток и поток демона
с небольшой задержкой. Когда обычный
поток выполняет блок finally , поток
демона
все еще ожидает в блоке try .
Когда обычный
поток завершает выполнение и завершает работу, JVM также завершает работу и не ждет, пока поток демона
завершит блок finally .
Вот результат:
Inside try
Inside try
Inside finally
4.4. JVM достигает бесконечного цикла
Вот блок try
, содержащий бесконечный цикл while
:
try {
System.out.println("Inside try");
while (true) {
}
} finally {
System.out.println("Inside finally");
}
Хотя это не относится к finally
, стоит отметить, что если блок try
или catch
содержит бесконечный цикл, JVM никогда не достигнет какого-либо блока за пределами этого цикла.
5. Распространенные ошибки
Есть несколько распространенных ошибок, которых следует избегать при использовании блока finally .
Хотя это совершенно законно, считается плохой практикой иметь оператор return
или генерировать исключение из блока finally
, и мы должны избегать этого любой ценой.
5.1. Игнорирует исключение
Оператор return
в блоке finally
игнорирует неперехваченное исключение:
try {
System.out.println("Inside try");
throw new RuntimeException();
} finally {
System.out.println("Inside finally");
return "from finally";
}
В этом случае метод игнорирует сгенерированное RuntimeException
и возвращает значение from finally
.
5.2. Игнорирует другие операторы возврата
Оператор return
в блоке finally
игнорирует любой другой оператор return в блоке try
или catch
. Выполняется только оператор return
в блоке finally :
try {
System.out.println("Inside try");
return "from try";
} finally {
System.out.println("Inside finally");
return "from finally";
}
В этом примере метод всегда возвращает «from finally»
и полностью игнорирует оператор return
в блоке try .
Эту ошибку может быть очень сложно обнаружить, поэтому нам следует избегать использования return
в блоках finally .
5.3. Изменяет то, что выброшено или возвращено
Кроме того, в случае генерации исключения из блока finally метод игнорирует выброшенные исключения или операторы
return
в блоках try
и catch :
try {
System.out.println("Inside try");
return "from try";
} finally {
throw new RuntimeException();
}
Этот метод никогда не возвращает значение и всегда генерирует исключение RuntimeException
.
Хотя мы не можем намеренно генерировать исключение из блока finally
, как в этом примере, мы все равно можем столкнуться с этой проблемой. Это может произойти, когда методы очистки, которые мы используем в блоке finally
, выдают исключение.
6. Заключение
В этой статье мы обсудили, что делают блоки finally
в Java и как их использовать. Затем мы рассмотрели разные случаи, когда JVM их выполняет, и несколько случаев, когда это может быть не так.
Наконец, мы рассмотрели некоторые распространенные ловушки, связанные с использованием блоков finally .
Как всегда, исходный код, используемый в этом руководстве, доступен на GitHub .