W3docs

Блок finally в Java

Выполняйте код очистки в Java с помощью блоков finally, которые работают всегда — независимо от того, было ли выброшено исключение.

Блок finally выполняется независимо от того, как завершился try — нормально, после пойманного исключения, после непойманного исключения или даже после раннего return. Именно в этом заключается его смысл: здесь размещают код очистки, который должен выполняться в любом случае. Закрытие файлового дескриптора, освобождение блокировки, восстановление состояния потока — всё то, чьё отсутствие оставило бы программу в худшем состоянии, чем до начала работы.

Структура

try {
  // risky code
} catch (SomeException e) {
  // optional — zero or more catches
} finally {
  // always runs after the try (and any matching catch)
}

Блок finally можно комбинировать с catch, использовать без catch (try { ... } finally { ... }) или с несколькими catch. Части свободно сочетаются между собой.

Что значит «всегда выполняется»

Блок finally выполняется, когда управление покидает try, независимо от способа выхода:

  • Нормальное завершение блока tryfinally выполняется после последнего оператора.
  • Исключение, выброшенное из tryfinally выполняется после завершения подходящего catch (или, если ни один catch не подходит, непосредственно перед распространением исключения).
  • return внутри try или catchfinally выполняется до того, как return фактически вступит в силу.
  • break или continue, покидающие tryfinally выполняется перед переходом.

Единственные способы пропустить finally: сама JVM завершает работу (System.exit, отключение питания, Runtime.halt), бесконечный цикл или взаимная блокировка внутри try, или Thread.stop (устаревший именно по этой причине). Для любого кода, написанного в обычном приложении, finally является железной гарантией.

try {
  return computeAnswer();    // even though there's a return here,
} finally {
  cleanup();                 // this runs before the method actually returns
}

Для чего нужен finally

По-честному: почти всегда — очистка ресурсов. До появления try-with-resources в Java 7 стандартный шаблон выглядел так:

InputStream in = null;
try {
  in = new FileInputStream(path);
  // read from in...
} catch (IOException e) {
  // handle
} finally {
  if (in != null) {
    try { in.close(); } catch (IOException ignored) { /* */ }
  }
}

Вложенный try/catch вокруг close() в finally — именно тот шаблонный шум, который try-with-resources был создан устранить. Мы рассмотрим это в следующей главе. Но понимание того, для чего нужен finally, делает новую конструкцию понятной.

Помимо ресурсов, finally полезен для:

  • Восстановления общего состояния, изменённого на время выполнения try — инкремент счётчика глубины, переключение флага, замена ThreadLocal.
  • Освобождения вручную приобретённых блокировок (Lock.lock()try { ... } finally { lock.unlock(); }).
  • Остановки таймеров или закрытия транзакций, не реализующих AutoCloseable.

Для чего finally не предназначен

Не пишите в finally логику, производящую результаты. Блок выполняется независимо от исхода — он не знает, успешно ли завершился try. Если вы поместите commit() в finally, коммит произойдёт даже при ошибке.

И не делайте return из finally. Это один из действительно опасных уголков языка:

try {
  return 1;
} finally {
  return 2;     // wins — the method returns 2 and the original return is lost
}

return (или throw) внутри finally переопределяет любой return или исключение из try. Исключение, которое должно было распространиться, молча отбрасывается. Именно поэтому большинство линтеров помечают это как ошибку. Правило: finally выполняет очистку; finally не производит значения.

Порядок выполнения

Когда присутствуют и catch, и finally:

try   { ... }
catch { ... }
finally { ... }

Порядок именно такой, какой вы ожидаете: выполняется try, если он выбрасывает исключение и catch подходит — выполняется catch, затем finally выполняется после того, что отработало. Если finally затем выбрасывает исключение, это новое исключение заменяет то, что распространялось из try или catch — ещё одна причина держать finally «тихим».

Рабочий пример

Мы оснащаем небольшую «транзакцию» блоками try/catch/finally и вызываем её тремя разными способами: нормальный успех, восстанавливаемый сбой и невосстанавливаемый. Блок finally выполняется во всех трёх случаях — в этом и состоит весь смысл.

java— editable, runs on the server

В третьем вызове doWork выбрасывает RuntimeException, которое локальный catch не перехватывает. Блок finally всё равно выполняется и выводит "release lock" до того, как исключение продолжает раскручивать стек вверх до main. Именно это свойство вы хотите от кода очистки — оно не зависит от того, успешно ли прошла обработка.

Что дальше

Шаблон «открыть ресурс, поработать с ним, закрыть в finally» настолько распространён, что Java создала для него специальный оператор. Продолжайте изучение: Java try-with-resources.

Практика

Практика
Что вернёт этот метод?\n\n```java\nstatic int demo() {\n try {\n return 1;\n } finally {\n return 2;\n }\n}\n```
Что вернёт этот метод?\n\n```java\nstatic int demo() {\n try {\n return 1;\n } finally {\n return 2;\n }\n}\n```
Was this page helpful?