Перейти к основному содержимому

Путеводитель по Java наконец Ключевое слово

· 7 мин. чтения

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 .