Saltar al contenido principal

Captura de excepciones

En el momento en que se produce una excepción se crea un objeto del tipo Exception correspondiente y se lanza la excepción. Esta puede ser capturada permitiendo realizar las acciones oportunas. Por ejemplo, si se produce una excepción de tipo IOException se creará una instancia de esa clase.

Los bloques try y catch

Una excepción tiene la siguiente forma:

try {
// Sección de código que puede lanzar una excepción
} catch (UnaExcepcion e) {
// Sección que maneja la excepción "UnaExcepcion"
} catch (OtraExcepcion e) {
// Sección que maneja la excepción "OtraExcepcion"
}

El bloque try delimita aquella o aquellas instrucciones donde se puede producir una excepción. Si esto sucede, el control del programa se transfiere al bloque catch definido para el tipo de excepción que se produjo, pasando como parámetro el objeto del tipo de la excepción que se creó.

El bloque catch define las instrucciones que se deberán ejecutar en caso de que se produzca un determinado tipo de excepción.

Consideraciones:

  • No puede existir código entre el bloque try y catch.
  • Puede haber varios bloques catch, pero no puede tener declarada la misma clase de excepción.
  • Aunque existan varios bloques catch, solo se ejecutará uno de ellos.
  • Los catch más específicos deben estar situados por delante de los más genéricos en relación a la jerarquía de herencia.

Multi-catch

El multi-catch es una funcionalidad que permite capturar múltiples excepciones en un mismo catch, reduciendo la duplicación de código. En vez de escribir varios bloques catch, puedes capturar varias excepciones con un único catch, separándolas con |.

try {
// Código que puede lanzar diferentes excepciones
} catch (Excepcion1 | Excepcion2 | Excepcion3 e) {
// Manejo de las excepciones
}

El objeto e pertenece al tipo común de las excepciones capturadas y puede ser usado dentro del catch. El método getMessage() devuelve un mensaje con el error producido.

Vamos a ver un ejemplo donde intentamos dividir un número y acceder a un índice de una lista. Estos errores pueden generar ArithmeticException e IndexOutOfBoundsException.

public class Main {
public static void main(String[] args) {
try {
int resultado = 10 / 0; // Esto lanza ArithmeticException
System.out.println(resultado);
int[] numeros = {1, 2, 3};
System.out.println(numeros[5]); // Esto lanza IndexOutOfBoundsException
} catch (ArithmeticException | IndexOutOfBoundsException e) {
System.out.println("Ocurrió un error: " + e.getMessage());
}
}
}

Herencia en la captura de excepciones

Java organiza las excepciones en una jerarquía de clases, donde cada clase hereda comportamiento de su clase padre.

Si añadimos este código:

catch (Exception e) {
...
}

Estamos capturando cualquier excepción que sea o herede de Exception.

Por ejemplo:

try {
lanzarError();
} catch (IOException e) {
System.out.println("Error de entrada/salida");
} catch (Exception e) {
System.out.println("Error genérico");
}
// Este código fuerza que se produzca una excepción de tipo FileNotFoundException
void lanzarError() throws FileNotFoundException {
throw new FileNotFoundException("Archivo no encontrado");
}

FileNotFoundException hereda de IOException:

  • El primer catch (IOException e) va a capturar la excepción aunque no sea exactamente un IOException, sino una subclase.
  • Si no pusieras ese catch, la excepción pasaría al catch (Exception e).

Si hacemos esto:

try {
lanzarError();
} catch (Exception e) {
System.out.println("Error genérico");
} catch (IOException e) {
System.out.println("Error de E/S");
}

IOException es hija de Exception, entonces nunca se llega al segundo catch. La herencia hace que todo lo que sea IOException ya lo capture el primer bloque.

El bloque finally

Las excepciones pueden tener un tercer bloque no obligatorio: finally.

Este bloque siempre se ejecuta después del bloque try, independientemente de que si se lanza o no una excepción.

try {
// Sección de código que puede lanzar una excepción
} catch (UnaExcepcion e) {
// Sección que maneja la excepción "UnaExcepcion"
} catch (OtraExcepcion e) {
// Sección que maneja la excepción "OtraExcepcion"
} finally {
//Sección que se ejecuta siempre
}

La traza de la pila (stack trace)

El método printStackTrace() de Throwable muestra la traza de la pila (stack trace) del error, indicando en qué punto del código ocurrió la excepción. Esto ayuda mucho en la depuración.

public class EjemploPrintStackTrace {
public static void main(String[] args) {
try {
int resultado = 10 / 0; // Lanza ArithmeticException
} catch (Exception e) {
e.printStackTrace(); // Muestra la traza del error
}
}
}

Esto produce la siguiente salida:

java.lang.ArithmeticException: / by zero
at EjemploPrintStackTrace.main(EjemploPrintStackTrace.java:5)
  • Indica que ocurrió una ArithmeticException porque se intentó dividir entre cero.
  • El error apareció en la línea 5 del archivo EjemploPrintStackTrace.java.