¿Un bloque finalmente siempre se ejecuta en Java?


2382

Teniendo en cuenta este código, ¿puedo estar absolutamente seguro de que el finallybloque siempre se ejecuta, sin importar lo que something()sea?

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

473
Si no fue así, la palabra clave debería ser nombrada probablyen su lugar.
Mediodía Seda




27
@BinoyBabu, finalizer ! = finally; finalizador == el finalize()método.
jaco0646

Respuestas:


2699

Sí, finallyse llamará después de la ejecución de los bloques de código tryo catch.

Los únicos momentos finallyque no se llamarán son:

  1. Si invocas System.exit()
  2. Si invocas Runtime.getRuntime().halt(exitStatus)
  3. Si la JVM se bloquea primero
  4. Si la JVM alcanza un bucle infinito (o alguna otra instrucción no interrumpible, sin terminación) en el bloque tryocatch
  5. Si el sistema operativo termina por la fuerza el proceso JVM; por ejemplo, kill -9 <pid>en UNIX
  6. Si el sistema host muere; por ejemplo, falla de energía, error de hardware, pánico del sistema operativo, etc.
  7. Si el finallybloque va a ser ejecutado por un subproceso de demonio y todos los demás subprocesos que no son de demonio salen antes, finallyse llama

44
En realidad thread.stop(), no necesariamente evita finallyque se ejecute el bloque.
Piotr Findeisen

181
¿Qué tal si decimos que el finallybloque se llamará después del trybloque y antes de que el control pase a las siguientes declaraciones? Eso es consistente con el bloque try que involucra un ciclo infinito y, por lo tanto, el bloque finalmente nunca se invoca.
Andrzej Doyle

99
También hay otro caso, cuando usamos bloques anidados try-catch-finally
ruhungry

77
Además, finalmente no se llama al bloque en caso de excepción lanzada por el hilo del demonio.
Amrish Pandey

14
@BinoyBabu - Eso es sobre el finalizador, no finalmente el bloqueo
avmohan

567

Código de ejemplo:

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

Salida:

finally trumps return. 
0

18
FYI: en C # el comportamiento es idéntico, aparte del hecho de que no está permitido reemplazar la declaración en la finallycláusula -con return 2;(Error del compilador).
Alexander Pacha

15
Aquí hay un detalle importante a tener en cuenta: stackoverflow.com/a/20363941/2684342
WoodenKitty

18
Incluso puede agregar una declaración de retorno en el bloque finalmente mismo, que luego anulará el valor de retorno anterior. Esto también descarta mágicamente las excepciones no controladas. En ese punto, debería considerar refactorizar su código.
Zyl

8
Eso no prueba realmente que finalmente triunfe el regreso. El valor de retorno se imprime desde el código de la persona que llama. No parece probar mucho.
Trimtab

20
Lo siento, pero esta es una demostración, no una prueba. Es solo una prueba si puede demostrar que este ejemplo siempre se comporta de esta manera en todas las plataformas Java, y que ejemplos similares también siempre se comportan de esta manera.
Stephen C

391

Además, aunque es una mala práctica, si hay una declaración de devolución dentro del bloque finalmente, prevalecerá sobre cualquier otra devolución del bloque regular. Es decir, el siguiente bloque devolvería falso:

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

Lo mismo con lanzar excepciones desde el último bloque.


95
Esta es una práctica REALMENTE mala. Consulte stackoverflow.com/questions/48088/… para obtener más información sobre por qué es malo.
John Meagher

23
Convenido. Un retorno dentro de finalmente {} ignora cualquier excepción lanzada en try {}. ¡De miedo!
neu242

8
@ dominicbri7 ¿Por qué crees que es una mejor práctica? ¿Y por qué debería ser diferente cuando la función / método es nulo?
corsiKa

8
Por la misma razón, NUNCA uso goto's en mis códigos C ++. Creo que las devoluciones múltiples hacen que sea más difícil de leer y más difícil de depurar (por supuesto, en casos realmente simples no se aplica). Supongo que es simplemente preferrence personnal y al final se puede lograr lo mismo usando cualquiera de los métodos
dominicbri7

16
Tiendo a usar una serie de devoluciones cuando ocurre algún tipo de caso excepcional. Como si (hay una razón para no continuar) regresar;
iHearGeoff

257

Aquí están las palabras oficiales de la Especificación del lenguaje Java.

14.20.2. Ejecución de try-finally y try-catch-finally

Una trydeclaración con un finallybloque se ejecuta ejecutando primero el trybloque. Entonces hay una opción:

  • Si la ejecución de la try bloque se completa normalmente, [...]
  • Si la ejecución del trybloque se completa abruptamente debido a throwun valor V , [...]
  • Si la ejecución del trybloque se completa abruptamente por cualquier otra razón R , entonces el finallybloque se ejecuta. Entonces hay una opción:
    • Si el bloque finalmente se completa normalmente, entonces la trydeclaración se completa abruptamente por la razón R .
    • Si el finallybloque se completa abruptamente por la razón S , entonces la trydeclaración se completa abruptamente por la razón S ( y la razón R se descarta ).

La especificación para returnrealmente hace esto explícito:

JLS 14.17 La declaración de devolución

ReturnStatement:
     return Expression(opt) ;

Una returndeclaración sin Expression intentos de transferir el control al invocador del método o constructor que lo contiene.

Una returndeclaración con un Expression intento de transferir el control al invocador del método que lo contiene; el valor de laExpression convierte en el valor de la invocación del método.

Las descripciones anteriores dicen " intentos de transferir el control " en lugar de simplemente " transferir el control " porque si hay alguna trydeclaración dentro del método o constructor cuyos trybloques contengan la returndeclaración, entonces cualquier finallycláusula de esas trydeclaraciones se ejecutará, en orden, de lo más interno a lo más externo. , antes de transferir el control al invocador del método o constructor. La finalización abrupta de una finallycláusula puede interrumpir la transferencia de control iniciada por una returndeclaración.


163

Además de las otras respuestas, es importante señalar que 'finalmente' tiene el derecho de anular cualquier excepción / valor devuelto por el bloque try..catch. Por ejemplo, el siguiente código devuelve 12:

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

Del mismo modo, el siguiente método no arroja una excepción:

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

Si bien el siguiente método lo arroja:

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

63
Cabe señalar que el caso del medio es precisamente la razón por la cual tener una declaración de devolución dentro de un bloque finalmente es absolutamente horrible (podría ocultar cualquier Throwable).
Dimitris Andreou

2
¿Quién no quiere un deprimido OutOfMemoryError? ;)
RecursiveExceptionException

Lo probé y suprime dicho error (¡sí!). También genera una advertencia cuando lo compilo (¡sí!). Y puede solucionarlo definiendo una variable de retorno y luego usando return retVal después del finallybloque, aunque eso, por supuesto, supone que suprimió algunas otras excepciones porque el código no tendría sentido de otra manera.
Maarten Bodewes

120

Intenté el ejemplo anterior con una ligera modificación.

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

Las salidas del código anterior:

finalmente triunfa el regreso.
2

Esto se debe a que cuando return i;se ejecuta itiene un valor 2. Después de esto, el finallybloque se ejecuta donde se asigna 12 iy luego System.outse ejecuta out.

Después de ejecutar el finallybloque, eltry bloque devuelve 2, en lugar de devolver 12, porque esta declaración de retorno no se ejecuta nuevamente.

Si va a depurar el código en Eclipse entonces obtendrá una sensación de que después de la ejecución System.outdel finallybloque de la returndeclaración del trybloque se ejecuta de nuevo. Pero este no es el caso. Simplemente devuelve el valor 2.


10
Este ejemplo es impresionante, agrega algo que no se ha mencionado en docenas de hilos finalmente relacionados. Creo que casi ningún desarrollador lo sabrá.
HopefulHelpful

44
¿Qué pasaría si ino fuera un primitivo, sino un objeto entero?
Yamcha

Me está costando entender este caso. docs.oracle.com/javase/specs/jls/se8/html/jls-14.html#jls-14.17 dice que, "Una declaración de retorno con una Expresión intenta transferir el control al invocador del método o cuerpo lambda que contiene ... Si la evaluación de la Expresión se completa normalmente, produce un valor V .. "Lo que podría adivinar de esta afirmación es que parece que return no evalúa la expresión nuevamente una vez que evalúa el valor V, por eso cambió i no afecta el valor devuelto, corrígeme.
meexplorer

Pero no pude encontrar ninguna prueba con respecto a esto, donde se menciona que return no evalúa la expresión nuevamente.
meexplorer

1
@meexplorer un poco tarde, pero se explica en JLS 14.20.2. Ejecución de try-finally y try-catch-finally - redactado un poco complicado, 14.17. La declaración de devolución también debe leerse
user85421

117

Aquí hay una elaboración de la respuesta de Kevin . Es importante saber que la expresión a devolver se evalúa antes finally, incluso si se devuelve después.

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

Salida:

X
finally trumps return... sort of
0

8
Importante saber.
Aminadav Glickshtein

Es bueno saberlo, y tiene sentido también. Parece que en realidad devolver el valor de retorno es lo que viene después finally. Calcular el valor de retorno ( printX()aquí) todavía viene antes.
Albert

¡Un buen ejemplo con los 3 puntos de "devolución"!
radistao

No El código anterior debe reemplazarse System.out.println("finally trumps return... sort of");conSystem.out.print("finally trumps return in try"); return 42;
Pacerier

54

Esa es la idea de un bloque finalmente. Te permite asegurarte de hacer limpiezas que de otro modo podrían saltarse porque regresas, entre otras cosas, por supuesto.

Finalmente se llama independientemente de lo que ocurra en el bloque de prueba (a menos que llame System.exit(int)o la máquina virtual Java se apague por alguna otra razón).


Esa es una respuesta extremadamente débil. stackoverflow.com/a/65049/715269
Gangnus

42

Una forma lógica de pensar en esto es:

  1. El código colocado en un bloque finalmente debe ejecutarse lo que ocurra dentro del bloque try
  2. Entonces, si el código en el bloque try intenta devolver un valor o lanzar una excepción, el artículo se coloca 'en el estante' hasta que el bloque finalmente pueda ejecutarse
  3. Debido a que el código en el bloque finalmente tiene (por definición) una alta prioridad, puede devolver o arrojar lo que quiera. En cuyo caso, todo lo que quede 'en el estante' se descarta.
  4. La única excepción a esto es si la VM se apaga completamente durante el bloque de prueba, por ejemplo, mediante 'System.exit'

10
¿Es esta simplemente "una forma lógica de pensarlo" o es realmente cómo se pretende que el bloque finalmente funcione de acuerdo con las especificaciones? Un enlace a un recurso de Sun sería muy interesante aquí.
matias

21

finalmente siempre se ejecuta a menos que haya una terminación anormal del programa (como llamar a System.exit (0) ..). entonces, tu sysout se imprimirá



18

El último bloque siempre se ejecuta a menos que haya una terminación anormal del programa, ya sea como resultado de un bloqueo de JVM o de una llamada a System.exit(0).

Además de eso, cualquier valor devuelto desde dentro del último bloque anulará el valor devuelto antes de la ejecución del último bloque, por lo tanto, tenga cuidado de verificar todos los puntos de salida cuando use try finally.


18

No, no siempre un caso de excepción es // System.exit (0); antes de que finalmente el bloque impida finalmente ser ejecutado.

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

Y esa es una de las razones por las que nunca deberías llamar a System.exit () ...
Franz D.

13

Finalmente, siempre se ejecuta ese es el punto, solo porque aparece en el código después de la devolución no significa que así es como se implementa. El tiempo de ejecución de Java tiene la responsabilidad de ejecutar este código al salir deltry bloque.

Por ejemplo, si tiene lo siguiente:

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

El tiempo de ejecución generará algo como esto:

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

Si se produce una excepción no detectada, el finallybloque se ejecutará y la excepción continuará propagándose.


11

Esto se debe a que asignó el valor de i como 12, pero no devolvió el valor de i a la función. El código correcto es el siguiente:

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

10

Porque un bloque finalmente siempre se llamará a menos que llame System.exit()(o el subproceso se bloquea).


10

De manera concisa, en la documentación oficial de Java (haga clic aquí ), está escrito que:

Si la JVM sale mientras se está ejecutando el código try o catch, entonces el bloque finalmente puede no ejecutarse. Del mismo modo, si el subproceso que ejecuta el código de prueba o captura se interrumpe o finaliza, el bloque finalmente puede no ejecutarse aunque la aplicación en su conjunto continúe.


10

La respuesta es simple .

ENTRADA:

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

SALIDA:

catch
finally

1
La respuesta es simple NO.
Christophe Roussy

1
@ChristopheRoussy ¿Cómo? ¿Puedes explicar por favor?
Conoce el

1
lea la respuesta aceptada, la pregunta original es sobre "¿Siempre se ejecutará" y no siempre. En su caso, lo hará, pero esto no responde a la pregunta original e incluso puede ser un principiante engañoso.
Christophe Roussy

entonces, ¿en qué caso no se ejecutará?
Conoce el

En todos los casos mencionados en otras respuestas, vea la respuesta aceptada con más de 1000 votos a favor.
Christophe Roussy

9

Sí, lo llamarán. Ese es el punto de tener finalmente una palabra clave. Si saltar fuera del bloque try / catch podría saltarse el bloque finalmente, era lo mismo que colocar System.out.println fuera del try / catch.


9

Sí, finalmente el bloque siempre se ejecuta. La mayoría de los desarrolladores usan este bloqueo al cerrar la conexión de la base de datos, el objeto del conjunto de resultados, el objeto de la declaración y también los utiliza en la hibernación de Java para revertir la transacción.


9

NO SIEMPRE

La especificación del lenguaje Java se describe cómo try- catch- finallyy try- catchbloques trabajan en 14.20.2
En ningún lugar se especifica que el finallybloque se ejecuta siempre. Pero en todos los casos en los que el try- catch- finallyy try- finallybloques completos se especifica que antes de la terminación finallydebe ser ejecutado.

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).

El JLS no garantiza que FIN se ejecute después de CODE . El JLS garantiza que si se ejecutan CODE y NEXT , FIN siempre se ejecutará después de CODE y antes de NEXT .

¿Por qué JLS no garantiza que el finallybloque siempre se ejecute después del trybloque? Porque es imposible Es poco probable pero posible que la JVM sea abortada (matar, estrellarse, apagarse) justo después de completar el trybloque pero antes de la ejecución del finallybloque. JLS no puede hacer nada para evitar esto.

Por lo tanto, cualquier software que, para su correcto comportamiento, dependa de que los finallybloques se ejecuten siempre después de que tryse completen sus bloques.

returnlas instrucciones en el trybloque son irrelevantes para este problema. Si la ejecución alcanza el código después de try- catch, finallyse garantiza que el finallybloque se habrá ejecutado antes, con o sin returninstrucciones dentro del trybloque.


8

Sí lo hará No importa lo que ocurra en el bloque try o catch a menos que se llame System.exit () o JVM se bloquee. Si hay alguna declaración de retorno en el (los) bloque (s), finalmente se ejecutará antes de esa declaración de retorno.


8

Sí lo hará. El único caso que no será es que JVM salga o se bloquee


8

Agregar a la respuesta de @ vibhash como ninguna otra respuesta explica lo que sucede en el caso de un objeto mutable como el siguiente.

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

Saldrá

sbupdated 

A partir de Java 1.8.162, este no es el resultado.
Sam

8

Intenté esto, es de un solo hilo.

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

El main Threadestará en waitestado para siempre, por finallylo tanto , nunca será llamado ,

entonces la salida de la consola no print String: después wait()ofinally

De acuerdo con @Stephen C, el ejemplo anterior es uno de los 3er caso mencionados aquí :

Agregando algunas más posibilidades de bucle infinito en el siguiente código:

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

Caso 2: si la JVM se bloquea primero

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: ¿Cómo se bloquea un JVM?

Caso 6: si el finallybloque va a ser ejecutado por un demonio Thready se llama a todas las otras Threadssalidas que no sean demonios antes 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("-------------------------------------------------");
}

salida: Esto no imprime "finalmente", lo que implica que "Finalmente bloque" en "hilo de demonio" no se ejecutó

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

44
Ver la respuesta aceptada. Esto es solo un caso extremo de "bucle infinito".
Stephen C

8

Considere el siguiente programa:

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

A partir de Java 1.8.162, el bloque de código anterior proporciona el siguiente resultado:

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

Esto significa que usar finallypara liberar objetos es una buena práctica como el siguiente código:

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.
    }
}

¿No debería estar sb.setLength(0)finalmente?
user7294900

sb.setLength (0) solo vaciará los datos en StringBuffer. Entonces, sb = null desasociará el objeto de la referencia.
Sam

¿No debería imprimirse "xyz" dos veces en la salida? Dado que la función se llamó dos veces, ¿por qué "finalmente" fue solo una vez?
fresko

2
Esta no es una buena práctica. Ese bloque finalmente sb = null;solo agrega código innecesario. Entiendo que quiere decir que un finallybloque es un buen lugar para liberar recursos como una conexión de base de datos o algo así, pero tenga en cuenta que su ejemplo podría confundir a los recién llegados.
Roc Boronat

1
@Samim Gracias, agregué las líneas System.out.println("---AGAIN2---"); System.out.println(sb);y ahora está más claro. Tal como estaba, el resultado estaba en contra de su tesis: p También agregué a su respuesta, pero la edición debe ser aceptada por un moderador o alguien así. De lo contrario, puede agregarlos
fresko

7

Eso es realmente cierto en cualquier idioma ... finalmente siempre se ejecutará antes de una declaración de devolución, sin importar dónde esté esa devolución en el cuerpo del método. Si ese no fuera el caso, el bloque finalmente no tendría mucho significado.


7

Además del punto sobre el retorno al reemplazar finalmente un retorno en el bloque try, lo mismo se aplica a una excepción. Un bloque finalmente que arroja una excepción reemplazará una devolución o excepción lanzada desde el bloque try.


7

finally ejecutará y eso es seguro.

finally no se ejecutará en los siguientes casos:

caso 1 :

Cuando estás ejecutando System.exit().

caso 2:

Cuando tu JVM / Thread se bloquea.

caso 3:

Cuando su ejecución se detiene en el medio manualmente.


6
  1. Finalmente Block siempre se ejecuta. A menos que y hasta que exista la declaración System.exit () allí (primera declaración en el bloque finalmente).
  2. Si system.exit () es la primera instrucción, finalmente el bloque no se ejecutará y el control saldrá del último bloque. Cada vez que la instrucción System.exit () ingresa finalmente en el bloque hasta que esa instrucción finalmente se ejecute y cuando aparece System.exit (), la fuerza de control sale completamente del bloque finalmente.

1
Esto ha sido respondido muchas veces, entonces, ¿qué nueva información agrega su respuesta?
Tom
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.