|
|
|
|
|
 |
|
 |
5.1 Definición:
Una excepción es un evento que ocurre durante la ejecución de un programa y detiene el flujo normal de la secuencia de instrucciones de ese programa; en otras palabras, una excepción es una condición anormal que surge en una secuencia de código durante su ejecución. Las excepciones en Java están destinadas, al igual que en el resto de los lenguajes que las soportan, para la detección y corrección de errores. Si hay un error, la aplicación no debería morirse y generar un core (o un crash en caso del DOS). Se debería lanzar (throw) una excepción que a su vez debería capturar (catch) y resolver la situación de error, o poder ser tratada finalmente (finally) por un gestor por defecto u omisión. Java sigue el mismo modelo de excepciones que se utiliza en C++. Utilizadas en forma adecuada, las excepciones aumentan en gran medida la robustez de las aplicaciones. La gestión de excepciones en Java proporciona un mecanismoexcepcionalmente poderoso para controlar programas que tengan muchas características dinámicas durante su ejecución. Las excepciones son formas muy limpias de manejar errores y problemas inesperados en la lógica del programa, y no deberían considerarse como un mecanismo general de ramificaciones o un tipo de sentencias de salto. Los lugares más indicados para utilizar excepciones son aquellos en los que se usan valores como 0 o 1, en C/C++, para indicar algún fallo funcional. Por ejemplo: #include int fd; fd = open( "leeme.txt" ); if( fd == -1 && errno == EEXIT ) fd = open( "defecto.txt" ); } En este programa C, si falla la primera sentencia open() por cualquiera de las 19 razones distintas de EEXIT por las que puede fallar, entonces el programa se continuaría ejecutando y moriría por alguna razón misteriosa más adelante, dejando atrás un problema de depuración complicado y frustrante. La versión Java del código anterior, tal como se muestra a continuación: FilterReader fr; try { Fr = new FilterReader( "leeme.txt" ); } catch( FileNotFoundException e ) { fr = new FilterReader( "defecto.txt" ); } Proporciona una oportunidad para capturar una excepción más genérica y tratar la situación con elegancia o, en el peor de los casos, imprimiría el estado de la pila de memoria. Por ello, que la utilización adecuada de las excepciones proporcionará un refinamiento profesional al código que cualquier usuario futuro de las aplicaciones que salgan de la mano del programador que las utilice agradecerá con toda seguridad.
5.2 Tipos de excepciones
5.3 Propagación de excepciones:
Las instrucciones que tenemos dentro de un bloque try a menudo contienen llamadas a métodos que a su vez pueden realizar llamadas a otros métodos y así sucesivamente. Cualquiera de los métodos llamados puede provocar una excepción y cualquiera de los métodos puede, o no, tratarla (con bloques catch). Ó Una excepción no tratada en un bloque se propaga hacia el bloque llamante. Este mecanismo de propagación continúa mientras no se trate la excepción o se llegue al método de nivel superior. Si la excepción no se trata en el método de nivel superior, se imprime un mensaje de error por consola. En el gráfico anterior se presenta el caso de un bloque try que contiene una llamada a un método Metodo_A que a su vez hace una llamada a Metodo_B, donde se produce una excepción. Como la excepción no es tratada en Metodo_B se propaga al bloque llamante (Metodo_A); a su vez la excepción no es tratada en Metodo_A, por lo que se propaga al bloque try, donde sí que es capturada por un catch. En el caso de que un método capture y trate la excepción, el mecanismo de propagación se termina y la ejecución de los métodos llamados y el bloque try del método llamante continúan como si la excepción no se hubiera producido: En muchas ocasiones, después de tratar la excepción a un nivel, resulta conveniente que también sea tratada a niveles superiores; por ejemplo, si estando leyendo de un fichero origen y escribiendo su contenido en un fichero destino nos encontramos con un error de lectura, además de tratar la excepción a ese nivel (cerrar los ficheros, etc.) sería deseable propagar la excepción al programa llamante para que informe al usuario y le permita hacer un nuevo intento si lo desea. Para propagar de forma explícita una excepción se emplea la palabra reservada throw seguida del objeto excepción. Ejemplo: try { // codigo Origen.CopiaFichero(Destino); // codigo } catch (IOException e) { System.out.println (“Error de lectura ¿Desea intentarlo de Nuevo?”); .......... } public void CopiaFichero (TipoFichero Destino) { try { // codigo } catch (IOException e) { // cerrar ficheros, etc. throw e; } En el ejemplo anterior, el método CopiaFichero, después de tratar la excepción, la propaga (throw) al método llamante, que a su vez hace otro tratamiento de la excepción. Estado de una excepción Como hemos visto, todas las excepciones derivan de la clase Throwable y tienen acceso a sus dos constructores y sus 7 métodos. El estado de las instancias de esta clase se compone de un String que sirve de mensaje indicando las características de la excepción y una pila de ejecución que contiene la relación de métodos que se encuentran en ejecución en el momento en el que se produce la excepción. Los métodos más significativos de la clase Throwable son: Para ilustrar la forma de uso de estos métodos, en la clase Excepcion2 modificamos el contenido de los bloques catch para imprimir la pila de ejecución: 16 catch (ArithmeticException e) { 17 System.out.println("Division por cero"); 18 e.printStackTrace(); 19 } 20 21 catch(IndexOutOfBoundsException e) { 22 System.out.println("Indice fuera del array"); 23 e.printStackTrace(); 24 } A la nueva clase la hemos llamado Excepcion3. La instrucción donde se realiza la división se encuentra en la línea 12 del método main, mientras que la instrucción donde se asigna el valor a la posición concreta del array se encuentra en la línea 13 del método main. A continuación se muestra el resultado de una posible ejecución del programa: Como se puede observar, cada instrucción e.printStackTrace() provoca la impresión de un breve mensaje acerca de la excepción, seguido de la pila de ejecución de los métodos del programa (en este caso sólo el método main) con indicación de la línea de cada método donde se ha producido la excepción.
5.4 Gestión de excepciones:
manejo de excepciones, lanzamiento de excepciones. Lanzamiento de excepciones: throw - throws Muchas veces el programador dentro de un determinado método deberá comprobar si alguna condición de excepción se cumple, y si es así lanzarla. Para ello se utilizan las palabras reservadas throw y throws. Por una parte la excepción se lanza mediante la sentencia throw: if ( condicion_de_excepcion == true ) throw new miExcepcion(); Se puede observar que hemos creado un objeto de la clase miExcepcion, puesto que las excepciones son objetos y por tanto deberán ser instanciadas antes de ser lanzadas. Aquellos métodos que pueden lanzar excepciones, deben cuáles son esas excepciones en su declaración. Para ello se utiliza la sentencia throws: tipo_devuelto miMetodoLanzador() throws miExcep1, miExcep2 { // Codigo capaz de lanzar excepciones miExcep1 y miExcep2 } Se puede observar que cuando se pueden lanzar en el método más de una excepción se deben indicar en sus declaraciones separadas por comas. Ejemplo de gestión de excepciones. Ahora que ya sabemos cómo funciona este sistema, conviene ver al menos un pequeño ejemplo, que ilustre al lector en el uso de las excepciones: // Creo una excepción personalizada class MiExcepcion extends Exception { MiExcepcion(){ super(); // constructor por defecto de Exception } MiExcepcion( String cadena ){ super( cadena ); // constructor param. de Exception } } // Esta clase lanzará la excepción class Lanzadora { void lanzaSiNegativo( int param ) throws MiExcepcion { if ( param < 0 ) throw new MiExcepcion( "Numero negativo" ); } } class Excepciones { public static void main( String[] args ) { // Para leer un fichero Lanzadora lanza = new Lanzadora(); FileInputStream entrada = null; int leo; try { entrada = new FileInputStream( "fich.txt" ); while ( ( leo = entrada.read() ) != -1 ) lanza.lanzaSiNegativo( leo ); entrada.close(); System.out.println( "Todo fue bien" ); } catch ( MiExcepcion e ){ // Personalizada System.out.println( "Excepcion: " + e.getMessage() ); } catch ( IOException e ){ // Estándar System.out.println( "Excepcion: " + e.getMessage() ); } finally { if ( entrada != null ) try { entrada.close(); // Siempre queda cerrado } catch ( Exception e ) { System.out.println( "Excepcion: " + e.getMessage() ); } System.out.println( "Fichero cerrado." ); } } } class Excepciones { public static void main( String[] args ) { // Para leer un fichero FileInputStream entrada = null; Lanzadora lanza = new Lanzadora(); int leo; try { entrada = new FileInputStream("fich.txt"); while ( ( leo = entrada.read() ) != -1 ) lanza.lanzaSiNegativo( leo ); System.out.println( "Todo fue bien" ); } catch ( MiExcepcion e ){ // Personalizada System.out.println( "Excepcion: " + e.getMessage() ); } catch ( IOException e ){ // Estándar System.out.println( "Excepcion: " + e.getMessage() ); } finally { entrada.close(); // Así el fichero siempre queda cerrado System.out.println( "Fichero cerrado" ); } } } Este programa lee un fichero (fichero.txt), y lee su contenido en forma de números. Si alguno de los números leídos es negativo, lanza una excepción MiExcepcion, Además gestiona la excepción IOException, que es una excepción de las que Java incluye y que se lanza si hay algún problema en una operación de entrada/salida. Ambas excepciones son gestionadas, imprimiendo su contenido (cadena de error) por pantalla. La salida de este programa, suponiendo un número negativo sería: Excepcion: Numero negativo - Fichero cerrado En el caso de que no hubiera ningún número negativo sería: Todo fue bien - Fichero cerrado En el caso de que se produjese un error de E/S, al leer el primer número, sería: Excepcion: java.io.IOException - Fichero cerrado.
5.5 Creación y manejo de excepciones definidas por el usuario:
Las excepciones predefinidas cubren las situaciones de error más habituales con las que nos podemos encontrar, relacionadas con el propio lenguaje y el hardware. Cuando se desarrollan aplicaciones existen otras situaciones de error de más ‘alto nivel’ relacionadas con la funcionalidad de nuestros programas. Imaginemos una aplicación informática que controla la utilización de los remontes de una estación de esquí: los pases de acceso a los remontes son personales e intransferibles y dispondrán de un código de barras que los identifica. Cada vez que un usuario va a hacer uso de un remonte debe introducir su pase de acceso en una máquina de validación, que acciona un torno y devuelve el pase. El sistema puede constar de un ordenador central al que le llegan telemáticamente los datos correspondientes a los códigos de barras de los pases que en cada momento se están introduciendo en cada máquina de validación de cada remonte; si un código de barras está en regla, el ordenador envía una orden de liberar el torno para permitir al usuario acceder al remonte. El ordenador central habitualmente recibirá códigos correctos utilizados en momentos adecuados, sin embargo, en ciertas ocasiones nos encontraremos con situaciones anómalas: 1 Código de barras ilegible 2 Código de barras no válido (por ejemplo correspondiente a un pase caducado) 3 Código de barras utilizado en otro remonte en un periodo de tiempo demasiado breve 4 etc. Ninguna de las situaciones anteriores se puede detectar utilizando excepciones predefinidas, puesto que su naturaleza está estrechamente relacionada con los detalles de la aplicación (no del lenguaje o el sistema informático), por lo que tendremos que recurrir al método tradicional de incluir condiciones (if, case, ...) de comprobación o bien hacer uso de excepciones definidas por nosotros mismos (excepciones no predefinidas). Antes de nada tenemos que tener en cuenta que el mecanismo de excepciones es muy lento en ejecución comparado con la utilización de instrucciones condicionales. Aunque el mecanismo de excepciones es elegante, debemos utilizarlo con prudencia: únicamente en situaciones que realmente son excepcionales. La primera situación anómala de nuestro ejemplo (código de barras ilegible) podría ser tratada como excepción o bien a través de alguna instrucción condicional, depende de la frecuencia con la que se nos presente esta situación. Si ocurre, por ejemplo, en aproximadamente un 0.5% de los casos, tendremos un claro candidato a ser tratado como excepción. Si ocurre, digamos en un 14% de los casos, deberíamos pensar en tratarlo con rapidez por medio de instrucciones condicionales. Para tratar la segunda situación anómala del ejemplo (código de barras no valido) deberíamos aplicar un razonamiento equivalente al que acabamos de realizar. Nuestro tercer caso nos presenta la situación de que un mismo pase de pistas ha podido ser duplicado (intencionadamente o por error), puesto que resulta físicamente imposible hacer uso de un remonte en un instante y volver a utilizarlo en otro remonte lejano medio minuto después. Probablemente esta situación se presenta muy rara vez y resultaría muy adecuado tratarla como excepción propia. Una vez razonada la utilidad de este tipo de excepciones, veremos la manera de definirlas en Java Definición de una excepción definida por el programador En programación orientada a objetos lo más adecuado es que las excepciones sean objetos, por lo que en Java definiremos las excepciones como clases. Nuestras clases de excepción, en general, heredarán de la clase Exception. En las clases que nos creemos a partir de Exception, incluiremos al menos el constructor vacío y otro que contenga un String como argumento. Este String se inicializa automáticamente con el nombre de la clase; la inicialización se realiza en la superclase Throwable. El texto que pongamos al hacer uso del segundo constructor se añadirá al nombre de la clase insertado por la superclase. A continuación se presenta una clase (ExPropia) muy simple que define una excepción propia: 1 public class ExPropia extends Exception { 2 3 ExPropia () { 4 super("Esta es mi propia excepcion"); 5 } 6 7 ExPropia (String s) { 8 super(s); 9 } 10 11 } En la línea 1 se define nuestra clase Expropia que hereda de Exception. La línea 3 define el constructor vacío, que en este caso añade el texto “Esta es mi propia excepcion” al nombre de la clase, quedando “ExPropia: Esta es mi propia excepcion”. La línea 7 define el constructor con un String como argumento; nos servirá para añadir el texto que deseemos al instanciar la clase. Utilización de una excepción definida por el programador Una vez que disponemos de una excepción propia, podremos programar la funcionalidad de nuestras aplicaciones provocando (lanzando) la excepción cuando detectemos alguna de las situaciones anómalas asociadas. En el siguiente ejemplo (ExPropiaClase) se presenta un método (línea 2) que levanta ( throw) la excepción ExPropia cuando se lee un cero (línea 4). La excepción ExPropia podría levantarse en diferentes secciones de código de esta u otras clases. No debemos olvidar indicar que nuestro método es susceptible de lanzar la excepción (línea 2). 1 public class ExPropiaClase { 2 public void Metodo() throws ExPropia { 3 if (Teclado.Lee_int() == 0) 4 throw new ExPropia(); 5 } 6 // otros metodos 7 } Finalmente, en clases de más alto nivel podemos programar secciones de código que recojan la excepción que hemos creado: 1 public class ExPropiaPrueba { 2 public static void main (String[] args) { 3 System.out.println("Hola"); 4 do 5 try { 6 ExPropiaClase Instancia = new ExPropiaClase(); 7 Instancia.Metodo(); 8 } 9 catch(ExPropia e) { 10 System.out.println(e); 11 } 12 while(true); 13 } 14 } Como se puede observar en la clase ExPropiaPrueba, podemos insertar código en un bloque try asociándole un bloque catch que recoge la excepción que hemos definido (ExPropia). A continuación se muestra un posible resultado de la ejecución de ExPropiaPrueba. Ejemplo En los apartados anteriores hemos definido y utilizado una excepción con sus constructores básicos, pero sin propiedades ni métodos adicionales. En ocasiones con el esquema seguido es suficiente, sin embargo, a menudo es adecuado dotar de un estado (propiedades) a la clase que define la excepción. Veamos este concepto con un ejemplo sencillo: deseamos programar la introducción por teclado de matrículas de vehículos en un país donde las matrículas se componen de 8 caracteres, siendo obligatoriamente el primero de ellos una letra. Si alguna matrícula introducida por el usuario no sigue el formato esperado se recogerá la interrupción oportuna y se escribirá un aviso en la consola. En el código siguiente se presenta la clase ExMatricula que hereda la funcionalidad de Exception (línea 1). Los constructores situados en las líneas 6 y 9 permiten iniciar el estado de la clase a través de los constructores de su superclase. En la línea 2 se define la propiedad MalFormada, que contiene información sobre la razón por la que el formato de la matrícula es incorrecto: tamaño inadecuado (línea 3) o inexistencia de la letra inicial (línea 5). El constructor de la línea 13 permite crear una excepción ExMatricula indicando la naturaleza del pro blema que obligará a levantar (trhow) esta excepción (ExMatricula.MAL_TAMANIO o ExMatricula.MAL_LETRA). En la línea 17 se suministra un método que permitirá, a los programas que reciben la excepción, saber la razón que ha provocado la misma. public class ExMatricula extends Exception { private int MalFormada = 0; static final int MAL_TAMANIO = -1; static final int MAL_LETRA = -2; ExMatricula() {} ExMatricula(String s) { super(s); } ExMatricula(int MalFormada) { this.MalFormada = MalFormada; } public int DimeProblema() { return MalFormada; } } Una vez creada la excepción ExMatricula podemos escribir el código que valida las matrículas. La siguiente clase (ExMatriculaValidar) realiza este cometido: el método Validar situado en la línea 6 comprueba el formato del parámetro suministrado y si es necesario tendrá capacidad de levantar (throws) la excepción ExMatricula. Si la longitud de la matrícula es distinta de 8 caracteres (línea se crea y levanta la excepción ExMatricula con estado inicial ExMatricula.MAL_TAMANIO (línea 9). Si el carácter inicial no es una letra (línea 11) se crea y levanta la excepción ExMatricula con estado inicial ExMatricula.MAL_LETRA (línea 12). El método privado (sólo es visible en esta clase) Una letra aísla el primer carácter y comprueba que es una letra empleando el método matches y la expresión regular “[A-Za-z]”. La utilización de expresiones regulares se incluye a partir de la versión 1.4 del JDK. 1 public class ExMatriculaValidar { 2 private boolean UnaLetra(String Matricula) { 4 return Matricula.substring(0,1).matches("[A-Za-z]"); 5 } 6 public void Validar(String Matricula) throws ExMatricula { 8 if (Matricula.length()!=8) 9 throw new ExMatricula(ExMatricula.MAL_TAMANIO); 10else 11if (!UnaLetra(Matricula)) 12throw new ExMatricula(ExMatricula.MAL_LETRA); 13else 14{} // matricula bien formada 15} 16} Con el método de validación de matrículas implementado, capaz de levantar la excepción ExMatricula, ya podemos escribir una aplicación que recoja las matrículas que introducen los usuarios. La recuperación de los errores de formato se escribirá en el bloque catch correspondiente: 1 public class ExMatriculaPrincipal { 2 public static void main (String[] args) { 3 ExMatriculaValidar LaMatricula = new ExMatriculaValidar(); 5 do 6 try { 7 System.out.println("Introduce la matricula: "); 8 String Matricula = Teclado.Lee_String(); 9 LaMatricula.Validar(Matricula); 10 } 11 catch(ExMatricula e) { 12 switch (e.DimeProblema()) { 13 case ExMatricula.MAL_TAMANIO: 14 System.out.println("Tamanio incorrecto"); 15 break; 16 case ExMatricula.MAL_LETRA: 17 System.out.println("Letra inicial no incluida"); 18 break; 19 default: 20 System.out.println("Matricula correcta"); 21 break; 22 } 23 } 24 while(true); 25 } 26 } En el código anterior existe un bucle infinito (líneas 5 y 24) que permite la validación de matrículas (líneas 7, 8 y 9). Habitualmente estas tres líneas se ejecutan repetitivamente; esporádicamente el usuario introducirá una matrícula con el formato erróneo y el método Validar levantará la excepción ExMatricula, que será recogida y tratada en el bloque catch (línea 11). En la línea 12 sabemos que se ha producido una excepción ExMatricula, pero no conocemos su causa exacta (tamaño incorrecto o primer carácter distinto de letra). El método DimeProblema que incluimos en la clase ExMatricula nos indica el estado de la excepción. Si el estado de la excepción es MAL_TAMANIO le indicamos esta situación al usuario que introdujo la matrícula con un tamaño incorrecto (líneas 14 y 15). En las líneas 17 y 18 hacemos lo propio con MAL_LETRA. |
|
 |
|
 |
|
|
|
|
|
|
|
|
|