Чи остаточний блок завжди виконується на 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");
}

472
Якщо цього не відбулося, слід навести ключове слово probablyзамість цього.
Полудень Шовк



2
Ефективне Java говорить інакше informit.com/articles/article.aspx?p=1216151&seqNum=7
Binoy Бабу

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

Відповіді:


2697

Так, finallyбуде викликано після виконання блоків tryабо catchкоду.

Єдиний раз, finallyколи не буде викликано:

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

44
Насправді thread.stop()не обов'язково перешкоджає виконанню finallyблоку.
Piotr Findeisen

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

9
є ще один випадок, коли ми використовуємо вкладені примірки всеосяжних нарешті блоки
ruhungry

7
також, нарешті, блок не викликається у випадку виключення, кинутого демоновою ниткою.
Amrish Pandey

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
FYI: У C # поведінка ідентична, крім того, що заміна оператора у finally-клаусі на return 2;не дозволяється (Компілятор-Помилка).
Олександр Пача

15
Ось важлива деталь , щоб бути в курсі: stackoverflow.com/a/20363941/2684342
WoodenKitty

18
Ви навіть можете додати оператор повернення до самого остаточного блоку, який потім замінить попереднє значення повернення. Це також магічно відкидає необроблені винятки. У цей момент вам слід розглянути можливість рефакторингу коду.
Зіль

8
Це насправді не доводить, що нарешті козирі повертаються. Повернене значення друкується з коду абонента. Це, здається, не доводить багато.
Trimtab

20
Вибачте, але це демонстрація, а не доказ. Це лише доказ, якщо ви можете показати, що цей приклад завжди так поводиться на всіх платформах Java, І що подібні приклади також завжди так поводяться.
Стівен C

391

Крім того, хоча це є поганою практикою, якщо в остаточному блоці є твердження про повернення, воно викличе будь-яке інше повернення із звичайного блоку. Тобто наступний блок поверне помилкове:

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

Те саме, що викидати винятки з остаточно блоку.


95
Це дійсно погана практика. Дивіться stackoverflow.com/questions/48088/… для отримання додаткової інформації про те, чому це погано.
Джон Міггер

23
Домовились. Повернення в остаточному підсумку {} ігнорує будь-який виняток, випробуваний {}. Страшно!
neu242

8
@ dominicbri7 Чому, на вашу думку, це краща практика? І чому має бути іншим, коли функція / метод недійсні?
corsiKa

8
З тієї ж причини я НІКОЛИ не використовую goto's у своїх C ++ кодах. Я думаю, що багаторазове повернення ускладнює читання і важче налагоджувати (звичайно, у дійсно простих випадках це не застосовується). Я думаю, що це просто особисті переваги, і, врешті-решт, ви можете досягти того ж, використовуючи будь-який метод
dominicbri7

16
Я схильний використовувати ряд повернень, коли трапляється якийсь винятковий випадок. Начебто, якщо (є причина не продовжувати) повернення;
iHearGeoff

257

Ось офіційні слова із специфікації мови Java.

14.20.2. Виконання спробу-нарешті та спробу-лов-нарешті

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

На додаток до інших відповідей важливо зазначити, що "нарешті" має право замінити будь-яке виключення / повернене значення блоком 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
Слід зазначити, що середній випадок є саме причиною того, що наявність заяви про повернення всередині остаточного блоку є абсолютно жахливою (це може приховати будь-яку Throwable).
Димитріс Андреу

2
Хто не хоче пригніченого OutOfMemoryError? ;)
РекурсивнеExceptionException

Я перевірив це, і це пригнічує таку помилку (вдається!). Він також генерує попередження, коли я складаю його (так!). І ви можете обійти це, визначивши змінну повернення , а потім , використовуючи return retVal після в finallyблоці, хоча це, звичайно , передбачає , що ви придушуються деякі інші винятки , тому що код не має сенсу інакше.
Maarten Bodewes

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виконується.

Після виконання finallyблоку tryблок повертає 2, а не повертає 12, оскільки ця операція повернення не виконується знову.

Якщо ви будете налагоджувати цей код в Eclipse , то ви отримаєте відчуття , що після виконання System.outз finallyблоку на returnзаяві tryблоку виконуються знову. Але це не так. Він просто повертає значення 2.


10
Цей приклад приголомшливий, він додає те, що не було згадано в десятках остаточно пов'язаних ниток. Я думаю, що ледве будь-який розробник буде знати це.
СподіваємосьДопомога

4
Що робити, якщо це iбув не примітивний, а цілий об'єкт.
Ямча

Мені важко зрозуміти цю справу. docs.oracle.com/javase/specs/jls/se8/html/jls-14.html#jls-14.17 говорить про те, що: " Звернення оператора з виразом намагається передати контроль до виклику методу або тіла лямбда, який містить це .... Якщо оцінка виразу завершується нормально, виробляючи значення V .. "Що я можу здогадатися з цього твердження, це - здається, що повернення не оцінює вираз знову, коли він оцінює значення V, тому змінив i не впливає на повернене значення, виправте мене.
meexplorer

Але я не знайшов жодних доказів щодо цього, де згадується, що повернення знову не оцінює вираз.
meexplorer

1
@meexplorer трохи пізно, але це пояснено в JLS 14.20.2. Виконання спроби-нарешті та спробу-лов-нарешті - сформульовано трохи складніше, 14.17. Заява про повернення також повинна бути прочитана
користувач85421

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
Важливо знати.
Амінадав Гліккштейн

Добре знати, і має сенс також. Здається, що насправді повернення вартості доходу - це те, що відбувається після finally. Розрахунок поверненої величини ( printX()тут) все ще передує.
Альберт

хороший приклад з усіма 3 "поверненими" балами!
radistao

Ні. Код вище повинен замінити System.out.println("finally trumps return... sort of");наSystem.out.print("finally trumps return in try"); return 42;
Pacerier

54

Ось і вся ідея остаточного блоку. Це дозволяє вам переконатися, що ви робите прибирання, які в іншому випадку можуть бути пропущені, тому що, крім іншого, ви повернетесь, звичайно.

Нарешті телефонує незалежно від того, що відбувається у блоці спробу ( якщо ви не дзвоните System.exit(int)або Java Virtual Machine не починається з якоїсь іншої причини).


Це вкрай слабка відповідь. stackoverflow.com/a/65049/715269
Гангнус

42

Логічний спосіб подумати над цим:

  1. Код, розміщений в остаточному блоці, повинен виконуватися все, що відбувається в блоці спробу
  2. Отже, якщо код у блоці спробу намагається повернути значення або викинути виняток, елемент розміщується "на полиці", поки нарешті блок не може виконати
  3. Оскільки код в остаточному блоці має (за визначенням) високий пріоритет, він може повернути або кинути все, що йому заманеться. У такому випадку все, що залишилося "на полиці", викидається.
  4. Єдиним винятком з цього є те, якщо VM повністю вимикається під час пробного блоку, наприклад, "System.exit"

10
Це просто "логічний спосіб подумати про це" чи це дійсно, як нарешті блок планується працювати відповідно до специфікацій? Посилання на ресурс Sun було б дуже цікавим тут.
matias

21

нарешті завжди виконується, якщо немає аномального завершення програми (наприклад, виклик System.exit (0) ..). Таким чином, ваша система буде надрукована



18

Остаточний блок завжди виконується, якщо немає аномального завершення програми, або в результаті аварії JVM або від виклику до System.exit(0).

Крім того, будь-яке значення, повернене зсередини блоку нарешті, замінить значення, повернене перед виконанням остаточного блоку, тому будьте уважні до перевірки всіх точок виходу при використанні спроби остаточно.


18

Ні, не завжди один виняток є // System.exit (0); перед тим, як нарешті блок запобігає остаточно виконувати

  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

Тому що, нарешті, блок завжди буде викликатися, якщо ви не зателефонуєте System.exit()(або потік обривається).


10

Точно в офіційній Документації Java (Клацніть тут ) написано, що -

Якщо JVM закінчується, поки виконується код спробу лову, блок остаточно може не виконуватись. Так само, якщо потік, який виконує код спробу вилову, перерваний або знищений, остаточний блок може не виконатись, хоча програма в цілому продовжується.


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

Так, це подзвонить. В цьому і полягає вся суть останнього ключового слова. Якщо вистрибнути з блоку try / catch можна просто пропустити остаточний блок, це було те саме, що поставити 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 виконується після КОДУ . JLS гарантує, що якщо CODE і NEXT виконуються, то FIN завжди буде виконуватися після CODE та до NEXT .

Чому JLS не гарантує, що finallyблок завжди виконується після tryблоку? Бо це неможливо. Навряд чи можливо, що JVM буде перервано (вбити, вийти з ладу, вимкнути живлення) відразу після заповнення tryблоку, але до його виконання finally. JLS нічого не може зробити, щоб цього уникнути.

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

returnінструкції в tryблоці не стосуються цього питання. Якщо виконання досягне коду після try- catch- finallyгарантується, що finallyблок буде виконаний раніше, з returnінструкціями всередині tryблоку або без них .


8

Так, буде. Незалежно від того, що відбувається у вашому блоці спробу вловлювання, якщо інше не викликано 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: Якщо СП виходить з ладу першим

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

Ref: Як ви розбиваєте 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("-------------------------------------------------");
}

вихід: Це не друкує "нарешті", що означає, що "Нарешті блок" у "демоновій нитці" не виконується

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
Дивіться прийняту відповідь. Це лише крайовий випадок "нескінченної петлі".
Stephen C

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" друкувати двічі на виході? Оскільки функцію викликали двічі, чому "нарешті" було лише один раз?
fresko

2
Це не є хорошою практикою. Нарешті блок із sb = null;просто додає непотрібний код. Я розумію, що ви маєте на увазі, що finallyблок - це гарне місце для звільнення ресурсів, таких як підключення до бази даних чи щось подібне, але майте на увазі, що ваш приклад може заплутати новачків.
Roc Boronat

1
@Samim Спасибі, я додав рядки System.out.println("---AGAIN2---"); System.out.println(sb);і зараз це вже зрозуміліше. Як і було, висновок був проти вашої тези: p Я також додав до вашої відповіді, але редагування повинно бути прийнято модератором чи кимось подібним. Ще ви можете додати їх
fresko

7

Це насправді вірно на будь-якій мові ... нарешті завжди виконується перед оператором return, незалежно від того, де це повернення знаходиться в тілі методу. Якби це не так, то нарешті блок не мав би великого значення.


7

На додаток до питання про повернення нарешті заміни повернення у блоці спробу, те саме стосується винятку. Нарешті, блок, який видає виняток, замінить повернення або виняток, викинутий з блоку спробу.


7

finally виконати, і це точно.

finally не буде виконано у наведених нижче випадках:

випадок 1:

Коли ви виконуєте System.exit().

випадок 2:

Коли ваш JVM / Thread виходить з ладу.

випадок 3:

Коли ваше виконання зупиняється між ними вручну.


6
  1. Нарешті, Блок завжди виконується. Якщо тільки і поки System.exit () існує оператора (перший вислів, нарешті, блок).
  2. Якщо system.exit () є першим оператором, тоді нарешті блок не буде виконаний, а контроль вийде з остаточного блоку. Кожен раз, коли оператор System.exit () потрапляє в остаточний блок, поки цей оператор остаточно блокується, і коли з'являється System.exit (), сила управління повністю виходить з остаточного блоку.

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