Las excepciones son eventos que ocurren durante la ejecución de un programa y hacen que éste salga de su flujo normal de instrucciones. Este mecanismo permite tratar los errores de una forma elegante, ya que separa el código para el tratamiento de errores del código normal del programa. Se dice que una excepción es lanzada cuando se produce un error, y esta excepción puede ser capturada para tratar dicho error.
Tenemos diferentes tipos de excepciones dependiendo del tipo de error que representen. Todas ellas descienden de la clase Throwable, la cual tiene dos descendientes directos:
Dentro de Exception, cabe destacar una subclase especial de excepciones denominada RuntimeException, de la cual derivarán todas aquellas excepciones referidas a los errores que comúnmente se pueden producir dentro de cualquier fragmento de código, como por ejemplo hacer una referencia a un puntero null, o acceder fuera de los límites de un array.
Estas RuntimeException se diferencian del resto de excepciones en que no son de tipo checked. Una excepción de tipo checked debe ser capturada o bien especificar que puede ser lanzada de forma obligatoria, y si no lo hacemos obtendremos un error de compilación. Dado que las RuntimeException pueden producirse en cualquier fragmento de código, sería impensable tener que añadir manejadores de excepciones y declarar que éstas pueden ser lanzadas en todo nuestro código.
Dentro de estos grupos principales de excepciones podremos encontrar tipos concretos de excepciones o bien otros grupos que a su vez pueden contener más subgrupos de excepciones, hasta llegar a tipos concretos de ellas. Cada tipo de excepción guardará información relativa al tipo de error al que se refiera, además de la información común a todas las excepciones. Por ejemplo, una ParseException se suele utilizar al procesar un fichero. Además de almacenar un mensaje de error, guardará la línea en la que el parser encontró el error.
Cuando un fragmento de código sea susceptible de lanzar una excepción y queramos tratar el error producido o bien por ser una excepción de tipo checked debamos capturarla, podremos hacerlo mediante la estructura try-catch-finally, que consta de tres bloques de código:
Podemos poner un bloque try sin catch pero con finally, ya que cuando la excepción no es de tipo checked no es obligatorio capturarla en catch, pero puede haber cosas que queramos hacer tanto si se produce como si no. También podemos poner un bloque try con catch y sin finally, cuando queremos capturar la excepción, y no hay ningún código que necesitemos que se ejecute inexorablemente.
Para el bloque catch además deberemos especificar el tipo o grupo de excepciones que tratamos en dicho bloque, pudiendo incluir varios bloques catch, cada uno de ellos para un tipo/grupo de excepciones distinto.
La sintaxis genérica de try - catch - finally será la siguiente (luego podemos omitir las partes que no necesitemos):
try { // Código regular del programa // Puede producir excepciones } catch(TipoDeExcepcion1 e1) { // Código que trata las excepciones de tipo // TipoDeExcepcion1 o subclases de ella. // Los datos sobre la excepción los encontraremos // en el objeto e1. } catch(TipoDeExcepcion2 e2) { // Código que trata las excepciones de tipo // TipoDeExcepcion2 o subclases de ella. // Los datos sobre la excepción los encontraremos // en el objeto e2. ... } catch(TipoDeExcepcionN eN) { // Código que trata las excepciones de tipo // TipoDeExcepcionN o subclases de ella. // Los datos sobre la excepción los encontraremos // en el objeto eN. } finally { // Código de finalización
}
Si como tipo de excepción especificamos un grupo de excepciones este bloque se encargará de la captura de todos los subtipos de excepciones de este grupo. Por lo tanto, si especificamos Exception capturaremos cualquier excepción de tipo Exception y sus subtipos, es decir, todas las excepciones existentes. Y si ponemos Throwable capturaremos cualquier tipo de error o excepción, ya que es el elemento padre de todos los errores y excepciones.
En el bloque catch pueden ser útiles algunos métodos de la excepción (que podemos ver en la API de la clase padre Exception):
String getMessage() void printStackTrace()
con getMessage() obtenemos una cadena descriptiva del error (si la hay). Con printStackTrace() se muestra por la salida estándar la traza de errores que se han producido (en ocasiones la traza es muy larga y no puede seguirse toda en pantalla con algunos sistemas operativos).
Unos ejemplos de uso:
try { ... // Aqui va el codigo que puede lanzar una excepcion } catch (Exception e) { System.out.println ("El error es: " + e.getMessage()); e.printStackTrace(); }
try
{
... // Aquí va un código que puede lanzar una excepción que no es obligatorio capturar
} finally {
... // Aquí pondremos lo que queramos que se haga salte o no salte la excepción.
}
Echa un vistazo a la clase sesion04.Ej1 que se proporciona en la plantilla. Verás que calcula la división de dos números (en el método divide), y se ejecuta dicha división en el método main, pasándole los dos números como argumentos en el parámetro args de dicho método.
Compila y prueba el funcionamiento de la clase, ejecutando una división sencilla, como por ejemplo 20 / 4, de la siguiente forma:
java sesion04.Ej1 20 4
Observa que la clase no realiza ciertos controles: no se comprueba si se le han pasado parámetros al main al ejecutar, ni si dichos parámetros son números correctos, ni si se puede realizar la división (no se podría dividir por un número que fuese cero, por ejemplo). Vamos a ir controlando dichos errores mediante lanzamiento y captura de excepciones, en los siguientes pasos.
public static void main(String[] args) { try { param1 = args[0]; param2 = args[1]; } catch (ArrayIndexOutOfBoundsException e) {
... } ... }
En el bloque catch pon el mensaje de error que consideres oportuno (por ejemplo, "Faltan parámetros"), y sal del programa (con System.exit(-1)). Prueba después la excepción capturada, ejecutando el programa con algo como:
java sesion04.Ej1 20
Debería capturar la excepción y mostrar por pantalla el mensaje "Faltan parametros" (o el que hayas elegido).
Pon el mensaje de error que quieras en el bloque catch (por ejemplo, "Formato incorrecto del parámetro"), y prueba después la excepción capturada, ejecutando el programa con algo como:
java sesion04.Ej1 20 hola
Debería capturar la excepción y mostrar por pantalla el mensaje "Formato incorrecto del parametro", o el que hayas elegido.
public static void main(String[] args) { int dividendo=0, divisor=0; try { dividendo = Integer.parseInt(args[0]); divisor = Integer.parseInt(args[1]); } catch (ArrayIndexOutOfBoundsException e) { ... } catch (NumberFormatException e2) { ... } System.out.println ("Resultado: " + divide(dividendo, divisor)); }Modifica el método main para dejarlo todo en un solo bloque try, y vuelve a probar los distintos casos de fallo indicados antes, para ver que el programa sigue comportándose igual.
Hemos visto cómo capturar excepciones que se produzcan en el código, pero en lugar de capturarlas también podemos hacer que se propaguen al método de nivel superior (desde el cual se ha llamado al método actual). Para esto, en el método donde se vaya a lanzar la excepción, se siguen 2 pasos:
public void lee_fichero() throws IOException, FileNotFoundException { // Cuerpo de la función }Podremos indicar tantos tipos de excepciones como queramos en la claúsula throws. Si alguna de estas clases de excepciones tiene subclases, también se considerará que puede lanzar todas estas subclases.
throw new IOException(mensaje_error);
public void lee_fichero() throws IOException, FileNotFoundException { ... throw new IOException(mensaje_error); ... }
Podremos lanzar así excepciones en nuestras funciones para indicar que algo no es como debiera ser a las funciones llamadoras. Por ejemplo, si estamos procesando un fichero que debe tener un determinado formato, sería buena idea lanzar excepciones de tipo ParseException en caso de que la sintaxis del fichero de entrada no sea correcta.
public void leeFich() throws ParseException { ... throw new ParseException("Error al procesar el fichero"); ... } ... public void otroMetodo() { try { leeFich(); } catch (ParseException e) { System.out.println ("Se ha producido un error al leer el fichero"); System.out.println ("El mensaje es: " + e.getMessage()); } }
NOTA: para las excepciones que no son de tipo checked no hará falta la cláusula throws en la declaración del método, pero seguirán el mismo comportamiento que el resto, si no son capturadas pasarán al método de nivel superior, y seguirán así hasta llegar a la función principal, momento en el que si no se captura provocará el error correspondiente.
Sigamos con la clase Ej1.java vista antes. Una vez tenemos comprobado que se pasan 2 parámetros, y que éstos son numéricos, sólo nos falta comprobar que se puede realizar una división correcta, es decir, que no se va a dividir por cero. Eso lo vamos a comprobar dentro del método divide.
Al principio del método, comprobamos si el segundo parámetro del mismo (el divisor) es cero, si lo es, lanzaremos una excepción indicando que el parámetro no es correcto. Dentro de los subtipos de excepciones de RuntimeException, tenemos una llamada IllegalArgumentException que nos puede ayudar. Probamos a poner estas líneas al principio del método divide:
public static int divide(int dividendo, int divisor) { if (divisor == 0) throw new IllegalArgumentException ("Divisor incorrecto"); ... }
Compila y ejecuta la clase. Prueba con algo como:
java sesion04.Ej1 20 0
¿Qué mensaje aparece? ¿Qué significa?
Captura la excepción en el main y muestra la traza del error con printStackTrace. Observa que capturar la excepción y hacer printStackTrace produce casi el mismo resultado que no capturarla. Esta técnica se utiliza en la fase de depurado para corregir errores del programa. Una vez depurado, ya se muestran mensajes de error más "amigables".
Además de utilizar los tipos de excepciones contenidos en la distribución de Java, podremos crear nuevos tipos que se adapten a nuestros problemas.
Para crear un nuevo tipo de excepciones simplemente deberemos crear una clase que herede de Exception o cualquier otro subgrupo de excepciones existente. En esta clase podremos añadir métodos y propiedades para almacenar información relativa a nuestro tipo de error. Por ejemplo:
public class MiExcepcion extends Exception { public MiExcepcion (String mensaje) { super(mensaje); } }
Además podremos crear subclases de nuestro nuevo tipo de excepción, creando de esta forma grupos de excepciones. Para utilizar estas excepciones (capturarlas y/o lanzarlas) hacemos lo mismo que lo explicado antes para las excepciones que se tienen definidas en Java. Por ejemplo:
public void unMetodo() throws MiException { ... throw new MiException("Error en el metodo"); ... } ... public void otroMetodo() { try { unMetodo(); } catch (MiException e) { ... } }
Vamos a terminar con la clase Ej1.java que estamos completando. Vamos a añadir una última excepción al método divide para comprobar que se realiza una división de números naturales (es decir, enteros mayores que 0).
public class NumeroNaturalException extends Exception { public NumeroNaturalException(String mensaje) { super(mensaje); } }¿Para qué sirve la llamada a super en el constructor en este caso?
public static int divide(int dividendo, int divisor) {
... if (dividendo < 0 || divisor < 0) throw new NumeroNaturalException("La division no es natural"); ... }Prueba a compilar la clase. ¿Qué error te da? ¿A qué puede deberse?
public static nt divide(int dividendo, int divisor) throws NumeroNaturalException { ... }NOTA: esta cláusula sólo hay que introducirla para excepciones que sean de tipo checked, es decir, que se pueda predecir que pueden pasar al ejecutar un programa. Dichas excepciones son los subtipos fuera de RuntimeException, y cualquier excepción que podamos crear nosotros. Por eso antes cuando utilizamos IllegalArgumentException no hemos tenido que añadirla, porque pertenece a RuntimeException.
Prueba a compilar la clase. ¿Qué error te da ahora? ¿Por qué?
java sesion04.Ej1 20 4 // 5 java sesion04.Ej1 20 // "Faltan parametros" java sesion04.Ej1 20 hola // "Formato incorrecto del parametro" java sesion04.Ej1 20 0 // Excepción de tipo IllegalArgumentException java sesion04.Ej1 20 -1 // "Error: La division no es natural"
java sesion04.Ej1 5 2 // "Error: La división entre 2 no es exacta"
PARA ENTREGAR
Guarda en la carpeta modulo2 de tu CVS los siguientes elementos para esta sesión: