1. Обзор
В этом уроке мы рассмотрим ключевое слово finally в Java. Мы увидим, как использовать его вместе с try/catch блоками при обработке ошибок. Хотя наконец предназначен для гарантии выполнения кода, мы обсудим исключительные ситуации, в которых 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
Кроме того, мы можем использовать a finally block с try block независимо от того, присутствует ли catch block :
try { System.out.println("Inside try"); } finally { System.out.println("Inside finally"); }
И мы получим результат:
Inside try Inside finally
2.3. Почему, наконец, Полезно
Обычно мы используем блок finally для выполнения кода очистки, такого как закрытие соединений, закрытие файлов или освобождение потоков, поскольку он выполняется независимо от исключения.
Примечание: |/try-with-resources также можно использовать для закрытия ресурсов вместо finally block.
3. Когда, наконец, Будет Выполнено
Давайте посмотрим на все перестановки, когда JVM выполняет , наконец, блоки, чтобы мы могли лучше понять это.
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 выполняет наконец блокируйте даже в случае необработанного исключения.
И выход был бы:
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 .
Мы уже можем ожидать, что если операционная система остановит нашу программу, то программа не получит возможности выполнить весь свой код. Есть также некоторые действия, которые мы можем предпринять, чтобы аналогичным образом предотвратить выполнение ожидающего окончательного блока.
4.1. Вызов системы.выход
В этом случае мы завершаем JVM, вызывая System.exit , и, следовательно, JVM не будет выполнять наш finally блок:
try { System.out.println("Inside try"); System.exit(1); } finally { System.out.println("Inside finally"); }
Это выводит:
Inside try
Это выводит:
Аналогично System.exit , вызову Runtime.halt также останавливает выполнение, и JVM не выполняет никаких , наконец, блоков:
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();
В этом примере запускаемый печатает “Inside try” , как только он входит в метод и ждет 1 секунду перед печатью “Inside finally” .
Здесь мы запускаем обычный поток и демон поток с небольшой задержкой. Когда поток regular выполняет блок finally , поток daemon все еще ожидает в блоке try . Когда обычный поток завершает выполнение и завершает работу, JVM также завершает работу и не ждет, пока демон поток завершит , наконец, блок.
Вот результат:
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 или выбрасывать исключение из , наконец, блока, и мы должны избегать этого любой ценой.
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 in finally blocks.
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 .