Bloque finally en Java
Ejecuta código de limpieza en Java con bloques finally que siempre se ejecutan, haya o no una excepción.
Un bloque finally se ejecuta sin importar cómo salga el try — finalización normal, excepción capturada, excepción no capturada, o incluso un return anticipado. Esa garantía es su razón de ser: es donde colocas la limpieza que debe ocurrir, sin excepción. Cerrar un manejador de archivo, liberar un bloqueo, restaurar el estado de un hilo — cualquier cosa cuya ausencia dejaría el programa en peor estado del que estaba.
La forma
try {
// risky code
} catch (SomeException e) {
// optional — zero or more catches
} finally {
// always runs after the try (and any matching catch)
}Puedes combinar finally con catch, sin ningún catch (try { ... } finally { ... }), o con múltiples catches. Las piezas se combinan libremente.
Qué significa "siempre se ejecuta"
Un bloque finally se ejecuta cuando el control abandona el try, independientemente de cómo:
- Finalización normal del bloque
try—finallyse ejecuta después de la última sentencia. - Una excepción lanzada desde
try—finallyse ejecuta después de que elcatchcorrespondiente termina (o, si ningún catch coincide, justo antes de que la excepción se propague). returndentro detryocatch—finallyse ejecuta antes de que el return surta efecto.breakocontinueque saldría deltry—finallyse ejecuta antes del salto.
Las únicas formas de saltarse finally son: que la propia JVM muera (System.exit, un corte de corriente, Runtime.halt), un bucle infinito o un deadlock dentro del try, o Thread.stop (que está obsoleto precisamente por esta razón). Para todo lo que escribas en código de aplicación normal, finally es una garantía firme.
try {
return computeAnswer(); // even though there's a return here,
} finally {
cleanup(); // this runs before the method actually returns
}Para qué sirve finally
La respuesta honesta es: limpieza de recursos, casi siempre. Antes de que Java 7 introdujera try-with-resources, la forma canónica era:
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) { /* */ }
}
}Ese try/catch anidado alrededor de close() en el finally es el tipo de ruido que try-with-resources fue diseñado para eliminar. Lo veremos en el próximo capítulo. Pero entender para qué sirve finally hace que el nuevo constructo tenga sentido.
Más allá de los recursos, finally es útil para:
- Restaurar estado compartido que mutaste durante el
try— incrementar un contador de profundidad, alternar un indicador, intercambiar unThreadLocal. - Liberar bloqueos adquiridos manualmente (
Lock.lock()→try { ... } finally { lock.unlock(); }). - Detener temporizadores o cerrar transacciones que no implementan
AutoCloseable.
Para qué no sirve finally
No escribas lógica que produzca resultados en finally. El bloque se ejecuta independientemente del resultado — no sabe si el try tuvo éxito. Si pones commit() en finally, harás commit incluso ante un fallo.
Y no hagas return desde finally. Este es uno de los rincones genuinamente peligrosos del lenguaje:
try {
return 1;
} finally {
return 2; // wins — the method returns 2 and the original return is lost
}El return (o throw) dentro de finally anula cualquier retorno o excepción del try. La excepción que estaba a punto de propagarse se descarta silenciosamente. La mayoría de los linters marcan esto como un error por esa razón. La regla: finally hace limpieza; finally no produce valores.
Orden de ejecución
Cuando están presentes tanto un catch como un finally:
try { ... }
catch { ... }
finally { ... }El orden es exactamente el que esperarías: try se ejecuta, si lanza y un catch coincide ese catch se ejecuta, y finally se ejecuta después de cualquiera de los dos. Si finally luego lanza, ese nuevo lanzamiento reemplaza lo que se estaba propagando desde el try o catch — otra razón para mantener finally tranquilo.
Un ejemplo elaborado
Instrumentamos una pequeña "transacción" con try/catch/finally y la llamamos de tres maneras distintas: éxito normal, fallo recuperable y fallo irrecuperable. finally se ejecuta en los tres casos, que es exactamente el objetivo.
En la tercera llamada, doWork lanza una RuntimeException que el catch local no captura. finally igualmente se ejecuta e imprime "release lock" antes de que la excepción siga propagándose hacia main. Esa es la propiedad que quieres del código de limpieza — no depende de si el manejo tuvo éxito.
Qué sigue
La forma "abrir un recurso, trabajar con él, cerrarlo en finally" es tan común que Java creó una sentencia dedicada para ello. Continúa en Java try-with-resources.