1. Introducción al bloque finally
Cuando trabajas con recursos — archivos, conexiones de red, bases de datos — es importante asegurarte de que se cierren o se liberen siempre, incluso si durante el proceso ocurre un error. En Java existe un bloque especial para ello — finally.
¿Cómo funciona finally?
El bloque finally es parte de la construcción try-catch-finally. El código dentro de finally se ejecuta siempre (si está escrito), independientemente de si hubo o no una excepción. Incluso si en try hubo un return o se lanzó una excepción — finally se ejecutará de todos modos (salvo que se apague el ordenador o se finalice el programa por la fuerza mediante System.exit(0)).
Sintaxis:
try {
// Código donde puede ocurrir una excepción
} catch (ExceptionType e) {
// Manejo del error
} finally {
// ¡Este código se ejecutará siempre!
}
Ejemplo
try {
System.out.println("Inicio de la ejecución");
int result = 10 / 0; // aquí se producirá un error
System.out.println("Resultado: " + result);
} catch (ArithmeticException e) {
System.out.println("Error: división por cero");
} finally {
System.out.println("Este código se ejecutará en cualquier caso");
}
Resultado de la ejecución:
Inicio de la ejecución
Error: división por cero
Este código se ejecutará en cualquier caso
¿Qué ocurre?
- En try intentamos dividir dos números y obtenemos un error.
- Si al dividir se produjo un error, catch lo manejará.
- ¡Pero! En cualquier caso finally se ejecutará y escribirá un mensaje en la consola.
2. finally sin catch
Pueden existir 3 variantes de la construcción:
- Completa: try-catch-finally
- Sin finally: try-catch
- Sin catch: try-finally
La tercera variante se utiliza cuando el método de un nivel superior será quien capture y maneje el error. Pero el bloque finally es necesario para garantizar la ejecución de cierto código:
- Cierre de archivos, conexiones de red, bases de datos.
- Liberación de cualquier recurso (por ejemplo, bloqueos).
- Registro en logs: escribir información sobre la finalización de la operación.
Ejemplo:
try {
System.out.println("Dividimos números");
int result = 10 / 0; // ¡error!
System.out.println("Resultado: " + result);
} finally {
System.out.println("El bloque finally se ha ejecutado");
}
Resultado:
Dividimos números
El bloque finally se ha ejecutado
Exception in thread "main" java.lang.ArithmeticException: / by zero
¿Cuándo NO se ejecuta finally?
Casi siempre se ejecuta. Excepciones — si:
- Se finaliza el programa por la fuerza con: System.exit(0).
- Se “mata” por la fuerza el hilo donde se ejecuta finally.
- Se apaga el ordenador.
3. Operador throw: cómo generar una excepción manualmente
A veces Java “lanza” excepciones por sí misma (por ejemplo, división por cero, acceso fuera de los límites de un array). Pero hay situaciones en las que tú quieres indicar explícitamente: «¡Esto es un error! ¡No puedo continuar la ejecución!». Para ello en Java existe el operador throw.
Analogía: Si estás en una tienda y ves un producto caducado — “lanzas” una queja. Así en el código: si algo está mal, lanzas una excepción.
Sintaxis de throw
throw new ExceptionType("Mensaje de error");
ExceptionType — cualquier clase que herede de Throwable (normalmente Exception o RuntimeException). Entre paréntesis — el mensaje que ayudará a entender qué salió mal.
Ejemplo: validación de argumentos del método
public static int safeDivide(int a, int b) {
if (b == 0) {
throw new IllegalArgumentException("El divisor no puede ser cero");
}
return a / b;
}
Uso:
public static void main(String[] args) {
try {
int result = safeDivide(10, 0);
System.out.println("Resultado: " + result);
} catch (IllegalArgumentException e) {
System.out.println("Error: " + e.getMessage());
}
}
Resultado:
Error: El divisor no puede ser cero
¿Cuándo usar throw?
- Validación de argumentos del método (por ejemplo, si llega null o datos no válidos).
- Comprobación del estado del objeto (por ejemplo, intentar retirar dinero de una cuenta con 0 euros).
- Dentro de catch — si quieres “relanzar” la excepción más arriba (por ejemplo, añadir información adicional).
4. Combinación de try-catch-finally y throw
A veces estas construcciones trabajan juntas. Por ejemplo, capturas un error, pero después decides lanzar uno propio, más informativo.
public static int parseAndDivide(String text, int divisor) {
try {
int number = Integer.parseInt(text);
if (divisor == 0) {
throw new IllegalArgumentException("El divisor no puede ser cero");
}
return number / divisor;
} catch (NumberFormatException e) {
throw new IllegalArgumentException("La cadena '" + text + "' no es un número");
} finally {
System.out.println("Intento de procesar la cadena: " + text);
}
}
Uso:
try {
int result = parseAndDivide("42a", 2);
System.out.println("Resultado: " + result);
} catch (IllegalArgumentException e) {
System.out.println("Error: " + e.getMessage());
}
Resultado:
Intento de procesar la cadena: 42a
Error: La cadena '42a' no es un número
Matiz importante: return y finally
¡Incluso si en el bloque try hay un return, finally se ejecutará igualmente!
public static int getValue() {
try {
return 10;
} finally {
System.out.println("¡finally se ejecutará igualmente!");
}
}
La llamada a getValue() mostrará:
¡finally se ejecutará igualmente!
5. Errores típicos al usar finally y throw
Error n.º 1: olvidaste cerrar un recurso sin finally.
Un problema muy común: abriste un archivo y no lo cerraste — se produjo una fuga de recursos. Usa siempre finally (o try-with-resources, del que hablaremos más adelante).
Error n.º 2: lanzaste una excepción, pero no la manejaste.
Si lanzas una excepción con throw, pero en ninguna parte la capturas (no hay try-catch), el programa terminará de forma anómala. Piensa siempre quién va a capturar tu excepción.
Error n.º 3: return en finally.
Si por error escribes return dentro de finally, “anulará” cualquier return o throw anteriores. Esto puede conducir a bugs muy difíciles de detectar. ¡No se recomienda en absoluto hacerlo!
public int tricky() {
try {
return 1;
} finally {
return 2; // PELIGRO: ¡devolverá 2, no 1!
}
}
Resultado: se devolverá 2, aunque en try había 1.
Error n.º 4: pérdida de información de la excepción.
Si capturas una excepción y luego lanzas otra nueva sin conservar la información de la anterior (e), pierdes la traza de llamadas, lo que dificulta la depuración. Es mejor escribir así:
catch (NumberFormatException e) {
throw new IllegalArgumentException("Error de conversión", e);
}
GO TO FULL VERSION