java-manejo-excepciones-try-catch-resources

Manejo de excepciones: try, catch y try-with-resources

  • 4 min

El manejo de excepciones permite interceptar un fallo, responder a él y liberar los recursos asociados.

En el artículo anterior vimos que una excepción se propaga por la pila de llamadas hasta encontrar un bloque capaz de manejarla. Si nadie lo hace, termina el hilo afectado.

Hoy vamos a aprender a poner la red de seguridad. Vamos a ver cómo capturar, gestionar y recuperarnos de los errores. Y, muy importante, cómo limpiar el desastre (cerrar archivos o conexiones) ocurra lo que ocurra.

La estructura básica: try y catch

El mecanismo es sencillo. “Intentamos” (try) ejecutar un código peligroso. Si falla, saltamos inmediatamente al bloque de “captura” (catch) para gestionar la crisis.

try {
    // Código peligroso
    int a = 10;
    int b = 0;
    int resultado = a / b; // Aquí explota (ArithmeticException)

    System.out.println("Esto NUNCA se imprimirá");

} catch (ArithmeticException e) {
    // Código de gestión de error
    System.out.println("¡Error! No puedes dividir por cero.");
    // Podemos ver detalles técnicos del error:
    // e.printStackTrace();
}

System.out.println("El programa sigue funcionando...");
Copied!

Capturando múltiples excepciones

A veces, un código puede fallar por varias razones (fichero no encontrado, formato incorrecto, error de red…). Podemos poner varios bloques catch en orden de especificidad.

El orden importa: Debes capturar primero las excepciones más específicas (hijas) y luego las genéricas (padres). Si pones catch (Exception e) el primero, se comerá todos los errores y los bloques de abajo darán error de compilación (código inalcanzable).

try {
    abrirFichero();
    leerNumero();
} catch (FileNotFoundException e) {
    System.out.println("Fichero no encontrado.");
} catch (NumberFormatException e) {
    System.out.println("El fichero no contiene un número válido.");
} catch (Exception e) {
    System.out.println("Ha ocurrido un error inesperado: " + e.getMessage());
}
Copied!

Desde Java 7, si vas a hacer lo mismo para varios errores, puedes usar el Multi-catch con la barra vertical |:

catch (FileNotFoundException | NumberFormatException e) {
    System.out.println("Error leyendo el fichero (no existe o formato mal).");
}
Copied!

El bloque finally: “pase lo que pase”

Imagina que abres una conexión a una base de datos en el try.

  • Si todo va bien, quieres cerrarla.
  • Si hay un error, también quieres cerrarla.

Si pones el cerrar() dentro del try, y falla antes de llegar ahí, la conexión se queda abierta (Memory Leak). Para eso existe finally. Este bloque se ejecuta al salir del try o del catch, incluso si hay un return. No puede garantizarse ante una terminación abrupta de la JVM, como System.exit, un fallo del proceso o un apagado del equipo.

Scanner sc = null;
try {
    sc = new Scanner(new File("datos.txt"));
    // Leer datos...
} catch (FileNotFoundException e) {
    System.out.println("Error");
} finally {
    // LIMPIEZA OBLIGATORIA
    System.out.println("Cerrando recursos...");
    if (sc != null) {
        sc.close();
    }
}
Copied!

try-with-resources

El código anterior con el finally es correcto, pero es verboso, feo y propenso a errores (¿qué pasa si sc.close() lanza otra excepción?).

Para solucionar esto, Java 7 introdujo el Try con Recursos. Es una sintaxis especial donde declaramos los recursos dentro de los paréntesis del try.

Cualquier objeto que implemente la interfaz AutoCloseable (como Scanner, FileInputStream, conexiones a BD) se cerrará automáticamente al terminar el bloque, tanto si acaba bien como si acaba mal.

// Fíjate en el paréntesis después del 'try'
try (Scanner sc = new Scanner(new File("datos.txt"))) {

    while (sc.hasNext()) {
        System.out.println(sc.nextLine());
    }
    // ¡NO HACE FALTA finally NI close()!
    // Java llama a sc.close() automáticamente aquí.

} catch (FileNotFoundException e) {
    System.out.println("No se encuentra el fichero");
}
Copied!

Usa try-with-resources cuando trabajes con recursos AutoCloseable, como flujos de E/S, conexiones de base de datos o sockets. Es más limpio y evita fugas de recursos del sistema.

Malas prácticas (anti-patrones)

Hay dos cosas que los novatos hacen y que enfadan mucho a los ingenieros senior.

Swallowing exceptions (tragarse el error)

try {
    algoPeligroso();
} catch (Exception e) {
    // Vacío. No hago nada.
}
Copied!

Esto es terrible. El error ocurre, el programa sigue, y tú no te enteras. Cuando el programa falle más adelante por culpa de esto, será imposible encontrar el origen. Como mínimo, imprime el error.

Pánico generalizado

try {
    // Todo mi programa
} catch (Exception e) {
    System.out.println("Algo pasó");
}
Copied!

No envuelvas todo tu main en un try-catch gigante. Intenta acotar los errores donde ocurren para poder reaccionar adecuadamente a cada uno.