Всегда ли выполняется блок finally в Java?


2382

Учитывая этот код, могу ли я быть абсолютно уверен, что finallyблок всегда выполняется, что бы something()это ни было?

try {  
    something();  
    return success;  
}  
catch (Exception e) {   
    return failure;  
}  
finally {  
    System.out.println("I don't know if this will get printed out");
}

473
Если это не так, ключевое слово должно быть названо probablyвместо.
Шелковый полдень



2
Эффективный Java говорит иначе. Informit.com/articles/article.aspx?p=1216151&seqNum=7
Бабу

27
@BinoyBabu, финализатор ! = finally; финализатор == finalize()метод.
jaco0646

Ответы:


2699

Да, finallyбудет вызываться после выполнения блоков кода tryили catch.

Единственные времена finallyне будут называться:

  1. Если вы вызываете System.exit()
  2. Если вы вызываете Runtime.getRuntime().halt(exitStatus)
  3. Если JVM падает в первую очередь
  4. Если JVM достигает бесконечного цикла (или некоторого другого не прерываемого, не завершающего оператора) в блоке tryorcatch
  5. Если ОС принудительно завершает процесс JVM; например, kill -9 <pid>в UNIX
  6. Если хост-система умирает; например, сбой питания, аппаратная ошибка, паника ОС и т. д.
  7. Если finallyблок будет выполняться потоком демона и все другие потоки, не являющиеся демонами, завершатся до finallyвызова

44
На самом деле thread.stop()не обязательно препятствует выполнению finallyблока.
Петр Финдейзен

181
Как насчет того , чтобы сказать , что finallyблок будет называться после того, как в tryблоке, и перед тем управление переходит к следующим заявлениям. Это согласуется с блоком try, включающим бесконечный цикл и, следовательно, блок finally, который фактически никогда не вызывается.
Анджей Дойл

9
есть и другой случай, когда мы используем вложенные блоки try-catch-finally
ruhungry

7
Кроме того, блок finally не вызывается в случае исключения, генерируемого потоком демона.
Амриш Пандей

14
@BinoyBabu - это о финализаторе, а не окончательном блоке
avmohan

567

Пример кода:

public static void main(String[] args) {
    System.out.println(Test.test());
}

public static int test() {
    try {
        return 0;
    }
    finally {
        System.out.println("finally trumps return.");
    }
}

Вывод:

finally trumps return. 
0

18
К сведению: в C # поведение идентично, за исключением того, что замена оператора в finally-clause return 2;недопустима (ошибка компилятора).
Александр Паша,

15
Вот важная деталь, о которой нужно знать: stackoverflow.com/a/20363941/2684342
WoodenKitty

18
Вы даже можете добавить инструкцию возврата в сам блок finally, который затем переопределит предыдущее возвращаемое значение. Это также волшебным образом отбрасывает необработанные исключения. На этом этапе вы должны рассмотреть возможность рефакторинга вашего кода.
Зил

8
Это на самом деле не доказывает, что наконец-то козыри вернутся. Возвращаемое значение печатается из кода вызывающего абонента. Не похоже, чтобы доказать много.
Trimtab

20
Извините, но это демонстрация, а не доказательство. Это только доказательство, если вы можете показать, что этот пример всегда ведет себя так на всех платформах Java, И что подобные примеры также всегда ведут себя так.
Стивен С

391

Кроме того, хотя это и плохая практика, если в блоке finally есть оператор return, он превзойдет любой другой возврат из обычного блока. То есть следующий блок вернул бы false:

try { return true; } finally { return false; }

То же самое с выбрасыванием исключений из блока finally.


95
Это действительно плохая практика. См. Stackoverflow.com/questions/48088/… для получения дополнительной информации о том, почему это плохо.
Джон Мигер

23
Согласовано. Возврат в finally {} игнорирует любое исключение, выданное в try {}. Страшно!
neu242

8
@ dominicbri7 Почему вы думаете, что это лучшая практика? И почему это должно отличаться, когда функция / метод не имеет значения?
CorsiKa

8
По той же причине я НИКОГДА не использую goto в моих кодах C ++. Я думаю, что множественные возвраты затрудняют чтение и затрудняют отладку (конечно, в действительно простых случаях это не применимо). Я полагаю, что это всего лишь личное предпочтение, и в конце концов вы можете добиться того же результата, используя любой из этих методов
dominicbri7

16
Я склонен использовать несколько возвратов, когда происходит какой-то исключительный случай. Например, если (есть причина не продолжать) вернуться;
iHearGeoff

257

Вот официальные слова из спецификации языка Java.

14.20.2. Выполнение try-finally и try-catch-finally

tryЗаявление с finallyблоком выполняется первым выполнением tryблока. Тогда есть выбор:

  • Если выполнение tryблока завершается нормально, [...]
  • Если выполнение tryблока завершается внезапно из- throwза значения V , [...]
  • Если выполнение tryблока завершается внезапно по какой-либо другой причине R , то finallyблок выполняется. Тогда есть выбор:
    • Если , наконец , блок завершается нормально, то tryоператор завершается преждевременно по причине R .
    • Если finallyблок завершается преждевременно по причине S , то tryоператор завершается преждевременно по причине S ( и причина R отбрасывается ).

Спецификация для returnфактически делает это явным:

JLS 14.17 Заявление о возврате

ReturnStatement:
     return Expression(opt) ;

returnЗаявление без каких - либо Expression попыток передать управление, вызвавшего метода или конструктора , который его содержит.

returnЗаявление с Expression попытками передать управление, вызвавший метод , который его содержит; значение Expressionстановится значением вызова метода.

В предыдущих описаниях говорится « пытается передать управление », а не просто « передает управление », потому что если tryв методе или конструкторе есть какие-либо операторы, чьи tryблоки содержат этот returnоператор, то любые finallyпредложения этих tryоператоров будут выполняться по порядку от самого внутреннего до самого внешнего перед передачей управления вызывающему методу или конструктору. Внезапное завершение finallyпредложения может нарушить передачу контроля, инициированную returnоператором.


163

В дополнение к другим ответам важно указать, что 'finally' имеет право переопределить любое исключение / возвращаемое значение с помощью блока try..catch. Например, следующий код возвращает 12:

public static int getMonthsInYear() {
    try {
        return 10;
    }
    finally {
        return 12;
    }
}

Точно так же следующий метод не вызывает исключение:

public static int getMonthsInYear() {
    try {
        throw new RuntimeException();
    }
    finally {
        return 12;
    }
}

В то время как следующий метод бросает это:

public static int getMonthsInYear() {
    try {
        return 12;          
    }
    finally {
        throw new RuntimeException();
    }
}

63
Следует отметить, что средний регистр является именно той причиной, по которой наличие оператора return внутри блока finally абсолютно ужасно (он может скрыть любой Throwable).
Димитрис Андреу

2
Кто не хочет удивляться OutOfMemoryError? ;)
RecursiveExceptionException

Я проверил это, и это подавляет такую ​​ошибку (yipes!). Он также генерирует предупреждение, когда я его компилирую (ууу!). И вы можете работать вокруг него, определяя переменную возвращения , а затем с помощью return retVal после того, как в finallyблоке, хотя это, конечно , предполагает , что вы подавляются некоторые другие исключения , потому что код не имеет смысла иначе.
Мартен Бодьюс

120

Я попробовал приведенный выше пример с небольшой модификацией

public static void main(final String[] args) {
    System.out.println(test());
}

public static int test() {
    int i = 0;
    try {
        i = 2;
        return i;
    } finally {
        i = 12;
        System.out.println("finally trumps return.");
    }
}

Вышеприведенный код выводит:

наконец козыри возвращаются.
2

Это потому, что когда return i;выполняется, iимеет значение 2. После этого выполняется finallyблок, где назначается 12, iа затем System.outвыполняется out.

После выполнения finallyблока tryблок возвращает 2, а не 12, потому что этот оператор возврата больше не выполняется.

Если вы будете отлаживать этот код в Eclipse , то вы получите ощущение , что после выполнения System.outиз finallyблока на returnзаявлении tryблока выполняются снова. Но это не так. Он просто возвращает значение 2.


10
Этот пример потрясающий, он добавляет то, что не было упомянуто в десятках, наконец, связанных тем. Я думаю, что едва ли любой разработчик будет знать это.
Хотелось бы надеяться, что

4
Что если бы iбыл не примитив, а объект Integer.
Ямча

Мне трудно понять это дело. docs.oracle.com/javase/specs/jls/se8/html/jls-14.html#jls-14.17 говорит, что «оператор return с выражением пытается передать управление вызывающему методу или телу лямбды, содержащему это .... Если вычисление Выражения завершается нормально, производя значение V .. "Что я мог догадаться из этого утверждения - кажется, что return не вычисляет выражение снова, когда оно оценивает значение V, поэтому изменил i не влияет на возвращаемое значение, поправьте меня.
meexplorer

Но я не нашел никаких подтверждений этому, где упоминалось, что return не оценивает выражение снова.
meexplorer

1
@meexplorer немного опоздал, но это объясняется в JLS 14.20.2. Выполнение try-finally и try-catch-finally - сформулировано немного сложнее, 14.17. Заявление о возврате также необходимо прочитать
user85421

117

Вот разработка ответа Кевина . Важно знать, что возвращаемое выражение вычисляется раньше finally, даже если оно возвращается после.

public static void main(String[] args) {
    System.out.println(Test.test());
}

public static int printX() {
    System.out.println("X");
    return 0;
}

public static int test() {
    try {
        return printX();
    }
    finally {
        System.out.println("finally trumps return... sort of");
    }
}

Вывод:

X
finally trumps return... sort of
0

8
Важно знать.
Аминадав Гликштайн

Полезно знать и имеет смысл. Кажется, что возвращение значения return - это то, что происходит после finally. Вычисление возвращаемого значения ( printX()здесь) по-прежнему предшествует ему.
Альберт

хороший пример со всеми 3 «возвращающимися» точками!
Радистао

Нет. Код выше следует заменить System.out.println("finally trumps return... sort of");наSystem.out.print("finally trumps return in try"); return 42;
Pacerier

54

Вот и вся идея окончательного блока. Это позволяет вам убедиться, что вы делаете очистки, которые в противном случае могли бы быть пропущены, потому что вы возвращаетесь, среди прочего, конечно.

Наконец вызывается независимо от того, что происходит вызывается в блоке try ( если только вы не вызываете System.exit(int)или не виртуальная машина Java по какой-то другой причине).


Это очень слабый ответ. stackoverflow.com/a/65049/715269
Гангнус

42

Логичный способ думать об этом:

  1. Код, помещенный в блок finally, должен выполняться независимо от того, что происходит в блоке try
  2. Поэтому, если код в блоке try пытается вернуть значение или сгенерировать исключение, элемент помещается «на полку» до тех пор, пока не будет выполнен блок finally.
  3. Поскольку код в блоке finally имеет (по определению) высокий приоритет, он может возвращать или выдавать все, что ему нравится. В этом случае все, что осталось «на полке», отбрасывается.
  4. Единственное исключение - это если виртуальная машина полностью отключается во время блока try, например, через «System.exit».

10
Это просто «логический способ думать об этом» или действительно, как блок finally должен работать в соответствии со спецификациями? Ссылка на ресурс Sun была бы очень интересна здесь.
Матиас

21

Наконец, всегда выполняется finally, если нет ненормального завершения программы (например, вызов System.exit (0) ..). Итак, ваш системный будет напечатан



18

Блок finally всегда выполняется, если не происходит аварийного завершения программы, вызванного сбоем JVM или вызовом System.exit(0) .

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


18

Нет, не всегда одним случаем исключения является // System.exit (0); прежде чем блок finally не позволяет выполнить, наконец, finally.

  class A {
    public static void main(String args[]){
        DataInputStream cin = new DataInputStream(System.in);
        try{
            int i=Integer.parseInt(cin.readLine());
        }catch(ArithmeticException e){
        }catch(Exception e){
           System.exit(0);//Program terminates before executing finally block
        }finally{
            System.out.println("Won't be executed");
            System.out.println("No error");
        }
    }
}

И это одна из причин, по которой вам никогда не следует вызывать System.exit () ...
Франц Д.

13

Наконец, всегда запускается, и в этом весь смысл, просто потому, что он появляется в коде после возврата, не означает, что так оно и реализовано. Среда выполнения Java отвечает за запуск этого кода при выходе из tryблока.

Например, если у вас есть следующее:

int foo() { 
    try {
        return 42;
    }
    finally {
        System.out.println("done");
    }
}

Среда выполнения сгенерирует что-то вроде этого:

int foo() {
    int ret = 42;
    System.out.println("done");
    return 42;
}

Если генерируется неперехваченное исключение, finallyблок будет запущен, и исключение продолжит распространяться.


11

Это потому, что вы присвоили значение i как 12, но не вернули значение i в функцию. Правильный код выглядит следующим образом:

public static int test() {
    int i = 0;
    try {
        return i;
    } finally {
        i = 12;
        System.out.println("finally trumps return.");
        return i;
    }
}

10

Потому что блок finally всегда будет вызываться, пока вы не вызовете System.exit()(или поток не завершится).


10

Вкратце, в официальной документации Java (Нажмите здесь ) написано, что -

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


10

Ответ прост ДА .

ВХОД:

try{
    int divideByZeroException = 5 / 0;
} catch (Exception e){
    System.out.println("catch");
    return;    // also tried with break; in switch-case, got same output
} finally {
    System.out.println("finally");
}

ВЫВОД:

catch
finally

1
Ответ прост НЕТ.
Кристоф Русси

1
@ChristopheRoussy Как? Объясните пожалуйста?
Встречайте

1
прочитайте принятый ответ, первоначальный вопрос о том, «всегда ли он будет исполнен», и не всегда. В вашем случае это будет, но это не отвечает на первоначальный вопрос и может даже вводить в заблуждение новичков.
Кристоф Русси

тогда в каком случае это не получит выполнить?
Встреча

Во всех случаях, упомянутых в других ответах, см. Принятый ответ с 1000+ ответами.
Кристоф Русси

9

Да, это будет называться. Вот и весь смысл иметь ключевое слово finally. Если выпрыгивание из блока try / catch может просто пропустить блок finally, это то же самое, что поместить System.out.println вне try / catch.


9

Да, наконец, блок всегда выполняется. Большинство разработчиков используют этот блок для закрытия соединения с базой данных, объекта набора результатов, объекта оператора, а также использует в спящем режиме Java для отката транзакции.


9

НЕ ВСЕГДА

Спецификация языка Java описывает, как try- catch-finally и try- catchблоки работают в 14.20.2
В каком месте он указывает , что finallyблок всегда выполняется. Но во всех случаях , в которых try- catch- finallyи try- finallyблоки заполнить это указать , что до завершения finallyдолжно быть выполнено.

try {
  CODE inside the try block
}
finally {
  FIN code inside finally block
}
NEXT code executed after the try-finally block (may be in a different method).

JLS не гарантирует, что FIN выполняется после CODE . JLS гарантирует, что если выполняются CODE и NEXT , то FIN всегда будет выполняться после CODE и до NEXT .

Почему JLS не гарантирует, что finallyблок всегда выполняется после tryблока? Потому что это невозможно. Маловероятно, но возможно, что JVM будет прервана (kill, crash, power off) сразу после завершения tryблока, но перед выполнениемfinally . JLS ничего не может сделать, чтобы избежать этого.

Таким образом, любое программное обеспечение, для которого его правильное поведение зависит от finallyблоков, всегда выполняющихся после того, как их tryблоки завершены, прослушиваются.

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


8

Да, это будет. Независимо от того, что происходит в вашем блоке try или catch, если не вызывается System.exit () или не происходит сбой JVM. если есть какой-либо оператор возврата в блоке (ах), то, наконец, будет выполнен до этого оператора возврата.


8

Да, это будет. Единственный случай, когда это не будет, JVM выходит или вылетает


8

Добавление к ответу @ vibhash, поскольку никакой другой ответ не объясняет, что происходит в случае изменяемого объекта, подобного приведенному ниже.

public static void main(String[] args) {
    System.out.println(test().toString());
}

public static StringBuffer test() {
    StringBuffer s = new StringBuffer();
    try {
        s.append("sb");
        return s;
    } finally {
        s.append("updated ");
    }
}

Будет выходной

sbupdated 

Начиная с Java 1.8.162 это не вывод.
Сэм

8

Я попробовал это, это однопоточный.

public static void main(String args[]) throws Exception {
    Object obj = new Object();
    try {
        synchronized (obj) {
            obj.wait();
            System.out.println("after wait()");
        }
    } catch (Exception ignored) {
    } finally {
        System.out.println("finally");
    }
}

main ThreadБудет на waitсостоянии навсегда, следовательно , finallyникогда не будет называться ,

поэтому вывод на консоль не будет print String: после wait()илиfinally

Согласен с @Stephen C, приведенный выше пример является одним из 3-х упоминаний здесь :

Добавим еще несколько таких возможностей бесконечного цикла в следующем коде:

// import java.util.concurrent.Semaphore;

public static void main(String[] args) {
    try {
        // Thread.sleep(Long.MAX_VALUE);
        // Thread.currentThread().join();
        // new Semaphore(0).acquire();
        // while (true){}
        System.out.println("after sleep join semaphore exit infinite while loop");
    } catch (Exception ignored) {
    } finally {
        System.out.println("finally");
    }
}

Случай 2: если JVM падает первым

import sun.misc.Unsafe;
import java.lang.reflect.Field;

public static void main(String args[]) {
    try {
        unsafeMethod();
        //Runtime.getRuntime().halt(123);
        System.out.println("After Jvm Crash!");
    } catch (Exception e) {
    } finally {
        System.out.println("finally");
    }
}

private static void unsafeMethod() throws NoSuchFieldException, IllegalAccessException {
    Field f = Unsafe.class.getDeclaredField("theUnsafe");
    f.setAccessible(true);
    Unsafe unsafe = (Unsafe) f.get(null);
    unsafe.putAddress(0, 0);
}

Ссылка: как вы разбили JVM?

Случай 6: если finallyблок будет выполняться демоном Threadи все другие не-демонные Threadsвыходы до того, как finallyбудет вызван.

public static void main(String args[]) {
    Runnable runnable = new Runnable() {
        @Override
        public void run() {
            try {
                printThreads("Daemon Thread printing");
                // just to ensure this thread will live longer than main thread
                Thread.sleep(10000);
            } catch (Exception e) {
            } finally {
                System.out.println("finally");
            }
        }
    };
    Thread daemonThread = new Thread(runnable);
    daemonThread.setDaemon(Boolean.TRUE);
    daemonThread.setName("My Daemon Thread");
    daemonThread.start();
    printThreads("main Thread Printing");
}

private static synchronized void printThreads(String str) {
    System.out.println(str);
    int threadCount = 0;
    Set<Thread> threadSet = Thread.getAllStackTraces().keySet();
    for (Thread t : threadSet) {
        if (t.getThreadGroup() == Thread.currentThread().getThreadGroup()) {
            System.out.println("Thread :" + t + ":" + "state:" + t.getState());
            ++threadCount;
        }
    }
    System.out.println("Thread count started by Main thread:" + threadCount);
    System.out.println("-------------------------------------------------");
}

выходные данные: это не выводит «finally», что означает, что «блок finally» в «потоке демона» не был выполнен

main Thread Printing  
Thread :Thread[My Daemon Thread,5,main]:state:BLOCKED  
Thread :Thread[main,5,main]:state:RUNNABLE  
Thread :Thread[Monitor Ctrl-Break,5,main]:state:RUNNABLE   
Thread count started by Main thread:3  
-------------------------------------------------  
Daemon Thread printing  
Thread :Thread[My Daemon Thread,5,main]:state:RUNNABLE  
Thread :Thread[Monitor Ctrl-Break,5,main]:state:RUNNABLE  
Thread count started by Main thread:2  
-------------------------------------------------  

Process finished with exit code 0

4
Смотрите принятый ответ. Это просто крайний случай «бесконечного цикла».
Стивен С

8

Рассмотрим следующую программу:

public class SomeTest {

    private static StringBuilder sb = new StringBuilder();

    public static void main(String args[]) {

        System.out.println(someString());
        System.out.println("---AGAIN---");
        System.out.println(someString());
        System.out.println("---PRINT THE RESULT---");
        System.out.println(sb.toString());
    }

    private static String someString() {

        try {
            sb.append("-abc-");
            return sb.toString();

        } finally {
            sb.append("xyz");
        }
    }
}

Начиная с Java 1.8.162, приведенный выше блок кода дает следующий вывод:

-abc-
---AGAIN---
-abc-xyz-abc-
---PRINT THE RESULT---
-abc-xyz-abc-xyz

это означает, что использование finallyдля освобождения объектов является хорошей практикой, такой как следующий код:

private static String someString() {

    StringBuilder sb = new StringBuilder();

    try {
        sb.append("abc");
        return sb.toString();

    } finally {
        sb = null; // Just an example, but you can close streams or DB connections this way.
    }
}

Разве это не должно быть sb.setLength(0)в конце концов?
user7294900

sb.setLength (0) просто очистит данные в StringBuffer. Таким образом, sb = null отсоединит объект от ссылки.
Сэм

Разве «xyz» не должен быть напечатан дважды на выходе? Поскольку функция была вызвана дважды, почему «наконец» был только один раз?
Фреска

2
Это не очень хорошая практика. Это, наконец, блок с sb = null;просто добавляет ненужный код. Я понимаю, что вы имеете в виду, что finallyблок - это хорошее место для освобождения ресурсов, таких как соединение с базой данных или что-то подобное, но имейте в виду, что ваш пример может запутать новичков.
Рок Боронат

1
@Samim Спасибо, я добавил строки, System.out.println("---AGAIN2---"); System.out.println(sb);и теперь это стало понятнее. Как бы то ни было, результат был против вашего тезиса: p Я также добавил к вашему ответу, но редактирование должно быть принято модератором или кем-то вроде этого.
Еще

7

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


7

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


7

finally выполнит и это точно.

finally не будет выполняться в следующих случаях:

Дело 1 :

Когда вы выполняете System.exit().

случай 2:

Когда ваша JVM / Thread падает.

случай 3:

Когда ваше выполнение остановлено между вручную.


6
  1. Наконец, Блок всегда исполняется. До тех пор, пока там не существует оператор System.exit () (первый оператор в блоке finally).
  2. Если system.exit () является первым оператором, то блок finally не будет выполнен, и управление выйдет из блока finally. Всякий раз, когда оператор System.exit () попадает в блок finally, пока этот оператор не завершит выполнение блока, а когда появится System.exit (), управляющая сила полностью выйдет из блока finally.

1
На этот вопрос отвечали много раз, так какую новую информацию добавляет ваш ответ?
Том
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.