1. Información general
En este tutorial, exploraremos la palabra clave finalmente en Java. Veremos cómo usarlo junto con los bloques try / catch en el manejo de errores. Aunque finalmente está destinado a garantizar la ejecución del código, discutiremos situaciones excepcionales en las que la JVM no lo ejecuta.
También discutiremos algunos errores comunes donde un fin bloque puede tener un resultado inesperado.
2. ¿Qué es finalmente?
finalmente define un bloque de código que usamos junto con la palabra clave try . Define el código que siempre se ejecuta después del try y cualquier bloque catch , antes de que se complete el método.
El bloque finalmente se ejecuta independientemente de si se lanza o detecta una excepción .
2.1. Un ejemplo rápido
Veamos finalmente en un bloque try-catch-finalmente :
try { System.out.println("The count is " + Integer.parseInt(count)); } catch (NumberFormatException e) { System.out.println("No count"); } finally { System.out.println("In finally"); }
En este ejemplo, independientemente del valor del recuento de parámetros , la JVM ejecuta el bloque finalmente e imprime "En finalmente" .
2.2. Usando finalmente sin un bloque de captura
Además, podemos usar un bloque finalmente con un bloque try independientemente de si hay un bloque catch presente :
try { System.out.println("Inside try"); } finally { System.out.println("Inside finally"); }
Y obtendremos el resultado:
Inside try Inside finally
2.3. ¿Por qué finalmente es útil?
Generalmente usamos el bloque finalmente para ejecutar código de limpieza como cerrar conexiones, cerrar archivos o liberar hilos, ya que se ejecuta independientemente de una excepción.
Nota: try-with-resources también se puede usar para cerrar recursos en lugar de bloquear finalmente .
3. Cuando finalmente se ejecuta
Echemos un vistazo a todas las permutaciones de cuándo se ejecuta la JVM finalmente se bloquea, para que podamos entenderlo mejor.
3.1. No se lanza ninguna excepción
Cuando se completa el bloque try , se ejecuta el bloque finalmente , incluso si no hubo excepción:
try { System.out.println("Inside try"); } finally { System.out.println("Inside finally"); }
En este ejemplo, no estamos lanzando una excepción del bloque try . Por lo tanto, la JVM ejecuta todo el código en los bloques try y finalmente .
Esto produce:
Inside try Inside finally
3.2. La excepción se lanza y no se maneja
Si hay una excepción y no se detecta, el bloque finalmente se sigue ejecutando:
try { System.out.println("Inside try"); throw new Exception(); } finally { System.out.println("Inside finally"); }
La JVM ejecuta el bloque finalmente incluso en el caso de una excepción no controlada.
Y la salida sería:
Inside try Inside finally Exception in thread "main" java.lang.Exception
3.3. Se lanza y se maneja la excepción
Si hay una excepción y es capturada por el bloque de captura , el bloque finalmente se sigue ejecutando:
try { System.out.println("Inside try"); throw new Exception(); } catch (Exception e) { System.out.println("Inside catch"); } finally { System.out.println("Inside finally"); }
En este caso, el bloque catch maneja la excepción lanzada, y luego la JVM ejecuta el finalmente bloque y produce la salida:
Inside try Inside catch Inside finally
3.4. Método Devuelve del bloque try
Incluso regresar del método no evitará que los bloques finalmente se ejecuten:
try { System.out.println("Inside try"); return "from try"; } finally { System.out.println("Inside finally"); }
Aquí, aunque el método tiene una declaración de retorno , la JVM ejecuta el bloque finalmente antes de entregar el control al método de llamada.
Obtendremos el resultado:
Inside try Inside finally
3.5. Método Devuelve del bloque de captura
Cuando el bloque de captura contiene una declaración de retorno , el bloque finalmente se sigue llamando:
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"); }
Cuando lanzamos una excepción del bloque try , el bloque catch maneja la excepción. Aunque hay una declaración de retorno en el bloque de captura , la JVM ejecuta el bloque finalmente antes de entregar el control al método de llamada, y genera:
Inside try Inside catch Inside finally
4. Cuando finalmente no se ejecuta
A pesar de que siempre esperamos que la JVM para ejecutar las sentencias dentro de un fin de bloque, hay algunas situaciones en las que la JVM no ejecutará un fin de bloque.
We might already expect that if the operating system stops our program, then the program would not get the chance to execute all of its code. There are also some actions we can take that will similarly prevent the execution of a pending finally block.
4.1. Invoking System.exit
In this case, we are terminating the JVM by calling System.exit and hence the JVM will not execute our finally block:
try { System.out.println("Inside try"); System.exit(1); } finally { System.out.println("Inside finally"); }
This outputs:
Inside try
4.2. Invoking halt
Similar to System.exit, a call to Runtime.halt also halts the execution and the JVM does not execute any finally blocks:
try { System.out.println("Inside try"); Runtime.getRuntime().halt(1); } finally { System.out.println("Inside finally"); }
Thus, the output will be:
Inside try
4.3. Daemon Thread
If a Daemon thread enters the execution of a try/finally block and all other non-daemon threads exit before the daemon thread executes the finally block, the JVM doesn’t wait for the daemon thread to finish the execution of finally block:
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();
In this example, the runnable prints “Inside try” as soon as it enters the method and waits for 1 second before printing “Inside finally”.
Here, we start the regular thread and the daemon thread with a small delay. When the regular thread executes the finally block, the daemon thread is still waiting within the try block. As the regular thread completes execution and exits, the JVM also exits and does not wait for the daemon thread to complete the finally block.
Here's the output:
Inside try Inside try Inside finally
4.4. JVM Reaches an Infinite Loop
Here's a try block which contains an infinite while loop:
try { System.out.println("Inside try"); while (true) { } } finally { System.out.println("Inside finally"); }
Though it's not specific to finally, it's worth mentioning that if the try or catch block contains an infinite loop, the JVM will never reach any block beyond that loop.
5. Common Pitfalls
There are some common pitfalls that we must avoid when we use the finally block.
Although it's perfectly legal, it's considered bad practice to have a return statement or throw an exception from a finally block, and we should avoid it at all costs.
5.1. Disregards Exception
A return statement in the finally block ignores an uncaught exception:
try { System.out.println("Inside try"); throw new RuntimeException(); } finally { System.out.println("Inside finally"); return "from finally"; }
In this case, the method ignores the RuntimeException thrown and returns the value “from finally”.
5.2. Ignores Other return Statements
A return statement in the finally block ignores any other return statement in the try or catch block. Only the return statement in the finally block executes:
try { System.out.println("Inside try"); return "from try"; } finally { System.out.println("Inside finally"); return "from finally"; }
In this example, the method always returns “from finally” and completely ignores the return statement in the try block. This could be a very difficult bug to spot, which is why we should avoid using return in finally blocks.
5.3. Changes What's Thrown or Returned
Also, in the case of throwing an exception from a finally block, the method disregards the exception thrown or return statements in the try and catch blocks:
try { System.out.println("Inside try"); return "from try"; } finally { throw new RuntimeException(); }
This method never returns a value and always throws a RuntimeException.
Si bien es posible que no lancemos intencionalmente una excepción del bloque finalmente como en este ejemplo, aún podemos encontrar este problema. Puede ocurrir cuando los métodos de limpieza que usamos en un bloque finalmente arrojan una excepción.
6. Conclusión
En este artículo, discutimos qué hacen los bloques finalmente en Java y cómo usarlos. Luego, analizamos diferentes casos en los que la JVM los ejecuta y algunos en los que es posible que no.
Por último, analizamos algunos errores comunes asociados con el uso de bloques finalmente .
Como siempre, el código fuente utilizado en este tutorial está disponible en GitHub.