Руководство по ключевому слову Java finally

1. Обзор

В этом руководстве мы исследуем ключевое слово finally в Java. Мы увидим, как использовать его вместе с блоками try / catch при обработке ошибок. Хотя функция finally предназначена для обеспечения выполнения кода, мы обсудим исключительные ситуации, в которых JVM не выполняет его.

Мы также обсудим некоторые распространенные ошибки, при которых блок finally может иметь неожиданный результат.

2. Что в итоге?

наконец, определяет блок кода, который мы используем вместе с ключевым словом try . Он определяет код, который всегда запускается после попытки и любого блока catch до завершения метода.

Блок finally выполняется независимо от того, выброшено ли исключение или перехвачено .

2.1. Быстрый пример

Давайте посмотрим, наконец, в блоке 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"); } 

В этом примере, независимо от значения счетчика параметров , JVM выполняет блок finally и печатает «In finally» .

2.2. Использование finally без блока 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 есть оператор возврата , JVM выполняет блок finally перед передачей управления вызывающему методу и выводит:

Inside try Inside catch Inside finally

4. Когда, наконец, не казнят

Хотя мы всегда ожидаем, что JVM выполнит инструкции внутри блока finally , в некоторых ситуациях JVM не выполнит блок finally .

We might already expect that if the operating system stops our program, then the program would not get the chance to execute all of its code. There are also some actions we can take that will similarly prevent the execution of a pending finally block.

4.1. Invoking System.exit

In this case, we are terminating the JVM by calling System.exit and hence the JVM will not execute our finally block:

try { System.out.println("Inside try"); System.exit(1); } finally { System.out.println("Inside finally"); }

This outputs:

Inside try

4.2. Invoking halt

Similar to System.exit, a call to Runtime.halt also halts the execution and the JVM does not execute any finally blocks:

try { System.out.println("Inside try"); Runtime.getRuntime().halt(1); } finally { System.out.println("Inside finally"); }

Thus, the output will be:

Inside try

4.3. Daemon Thread

If a Daemon thread enters the execution of a try/finally block and all other non-daemon threads exit before the daemon thread executes the finally block, the JVM doesn’t wait for the daemon thread to finish the execution of finally block:

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();

In this example, the runnable prints “Inside try” as soon as it enters the method and waits for 1 second before printing “Inside finally”.

Here, we start the regular thread and the daemon thread with a small delay. When the regular thread executes the finally block, the daemon thread is still waiting within the try block. As the regular thread completes execution and exits, the JVM also exits and does not wait for the daemon thread to complete the finally block.

Here's the output:

Inside try Inside try Inside finally

4.4. JVM Reaches an Infinite Loop

Here's a try block which contains an infinite while loop:

try { System.out.println("Inside try"); while (true) { } } finally { System.out.println("Inside finally"); }

Though it's not specific to finally, it's worth mentioning that if the try or catch block contains an infinite loop, the JVM will never reach any block beyond that loop.

5. Common Pitfalls

There are some common pitfalls that we must avoid when we use the finally block.

Although it's perfectly legal, it's considered bad practice to have a return statement or throw an exception from a finally block, and we should avoid it at all costs.

5.1. Disregards Exception

A return statement in the finally block ignores an uncaught exception:

try { System.out.println("Inside try"); throw new RuntimeException(); } finally { System.out.println("Inside finally"); return "from finally"; }

In this case, the method ignores the RuntimeException thrown and returns the value “from finally”.

5.2. Ignores Other return Statements

A return statement in the finally block ignores any other return statement in the try or catch block. Only the return statement in the finally block executes:

try { System.out.println("Inside try"); return "from try"; } finally { System.out.println("Inside finally"); return "from finally"; }

In this example, the method always returns “from finally” and completely ignores the return statement in the try block. This could be a very difficult bug to spot, which is why we should avoid using return in finally blocks.

5.3. Changes What's Thrown or Returned

Also, in the case of throwing an exception from a finally block, the method disregards the exception thrown or return statements in the try and catch blocks:

try { System.out.println("Inside try"); return "from try"; } finally { throw new RuntimeException(); }

This method never returns a value and always throws a RuntimeException.

Хотя мы не можем намеренно генерировать исключение из блока finally, как в этом примере, мы все равно можем столкнуться с этой проблемой. Это может произойти, когда методы очистки, которые мы используем в блоке finally, вызывают исключение.

6. Заключение

В этой статье мы обсудили, что блоки finally делают в Java и как их использовать. Затем мы рассмотрели разные случаи, когда JVM выполняет их, и несколько случаев, когда это не так.

Наконец, мы рассмотрели некоторые распространенные ошибки, связанные с использованием блоков finally .

Как всегда, исходный код, используемый в этом руководстве, доступен на GitHub.