Excepciones
Las excepciones son la manera que ofrece Java de manejar los errores en
tiempo de ejecución. Muchos lenguajes imperativos, cuando tenían un error de
este clase lo que hacían era detener la ejecución del programa. Las excepciones
nos permiten escribir código que nos permita manejar ese error y continuar
(si lo estimamos conveniente) con la ejecución del programa.
El error en ejecución más clásico en Java es el de desbordamiento. Es
decir, el intento de acceso a una posición de un vector que no existe. Por
ejemplo:
Desbordamiento.java
public class Desbordamiento {
static String mensajes[] = {"Primero", "Segundo", "Tercero" };
public static void main(String[] args) {
for (int i=0; i<=3; i++)
System.out.println(mensajes[i]);
System.out.println("Ha finalizado la ejecución");
}
}
Este programa tendrá un serio problema cuando intente acceder a
mensajes[3], pues no existe dicho valor. Al
ejecutarlo nos dirá lo siguiente (o algo parecido):
Primero
Segundo
Tercero
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException
at Desbordamiento.main(Desbordamiento.java, Compiled Code)
Nos da un error de ejecución (en esta terminología se diría que se lanzó
una excepción) al intentar acceder a dicho valor inexistente. Vemos que,
por ahora, el comportamiento de nuestro código es el mismo que en los lenguajes
imperativos. Cuando encuentra el error, se para la ejecución. Ahora veremos
como evitar esto.
try...catch...finally
Existe una estructura que nos permitirá capturar excepciones, es decir,
reaccionar a un error de ejecución. De este modo podremos imprimir mensajes
de error "a la medida" y continuar con la ejecución del programa si consideramos
que el error no es demasiado grave. Para ver como funcionan vamos a modificar
el ejemplo anterior, pero asegurandonos ahora de que capturamos las excepciones:
NuestroPrimerCatch.java
public class NuestroPrimerCatch {
static String mensajes[] = {"Primero", "Segundo", "Tercero" };
public static void main(String[] args) {
try {
for (int i=0; i<=3; i++)
System.out.println(mensajes[i]);
}
catch ( ArrayIndexOutOfBoundsException e ) {
System.out.println("El asunto se nos ha desbordado");
}
finally {
System.out.println("Ha finalizado la ejecución");
}
}
}
Dentro del bloque que sigue a try colocamos
el código a ejecutar. Es como si dijeramos que vamos a intentar ejecutar dicho
código a ver qué pasa. Después de try deberemos
colocar al menos un bloque catch o un bloque
finally, pudiendo tener ambos e incluso
más de un bloque catch.
En el bloque finally ponemos el código que
se ejecutará siempre, tanto si se lanza la excepción como si no. Su utilidad no
es mucha, ya que si se permite continuar con la ejecución del programa basta
con poner el código después del bloque try...catch.
En nuestro ejemplo podríamos haber puesto lo siguiente:
try {
for (int i=0; i<=3; i++)
System.out.println(mensajes[i]);
}
catch ( ArrayIndexOutOfBoundsException e ) {
System.out.println("El asunto se nos ha desbordado");
}
System.out.println("Ha finalizado la ejecución");
Y habría funcionado exactamente igual. La miga está en los bloques
catch.
Clase Exception
Cuando se lanza una excepción, en nuestro mundo orientado objetos lo que
se hace es lanzar una instancia de Exception o
de una clase derivada de él. Normalmente las clases derivadas no añaden mucha
funcionalidad (muchas veces ninguna en absoluto), pero al ser distintas nos
permiten distinguir entre los distintos tipos de excepciones.
En el programa anterior, por ejemplo, en el bloque catch capturábamos
una excepción del tipo ArrayIndexOutOfBoundsException,
ignorando cualquier otro tipo de excepción.
Esta clase tiene como cositas interesantes dos constructores y dos métodos
(tiene más métodos pero sólo vamos a ver éstos):
- Exception e = new Exception()
- Crea una excepción si ningún mensaje específico.
- Exception e = new Exception ( String )
- Crea una excepción con un mensaje que detalla el tipo de excepción.
- String e.getMessage()
- Devuelve el mensaje detallado si existe o
null en caso contrario.
- void e.printStackTrace()
- Escribe por la salida de error estándar una traza que nos permitirá
localizar donde se generó el error. Es muy útil para depurar, pero no es
conveniente que lo usuarios finales vean estas cosas.
Capturar excepciones
Ahora sabemos lo suficiente como para entender cómo funcionan los
catch. Entre paréntesis, cual parámetro se pone
la declaración de una excepción, es decir, el nombre de una clase derivada de
Exception (o la misma
Exception) seguido por el nombre de la variable.
Si se lanza una excepción que es la que deseamos capturar o una derivada de la
misma se ejecutará el código que contiene el bloque. Así, por ejemplo:
catch (Exception e) {
...
}
Se ejecutará siempre que se produzca una excepción del tipo que sea, ya
que todas las excepciones se derivan de Exception.
No es recomendable utilizar algo así ya que estamos capturando cualquier tipo
de excepciones sin saber si eso afectará a la ejecución del programa o no.
Se pueden colocar varios bloques catch. En
ese caso, se comprobará en orden si la excepción lanzada es la que deseamos
capturar y si no pasa a comprobar la siguiente. Eso sí, sólo se ejecuta un
bloque catch. En cuanto captura la excepción
deja de comprobar los demás bloques. Por eso, lo siguiente:
catch (Exception e) {
...
}
catch (DerivadaDeException e) {
...
}
Daría error, ya que el segundo bloque no podría ejecutarse nunca.