Manejo de Excepciones H. Tejeda Abril 2016
´Indice 1. Introducci´ on
1
2. Intentar c´ odigo y capturar excepciones
5
3. Lanzar y atrapar excepciones m´ ultiples
11
4. Bloque finally
14
5. Ventajas del manejo de excepci´ on
16
6. Especificar excepciones que un m´ etodo puede lanzar
18
7. Traza de excepciones en la pila de llamadas
21
8. Creaci´ on de clases Exception propias
23
9. Uso de afirmaciones
26
1.
Introducci´ on
Una excepci´ on es una condici´ on inesperada o error. Los programas que se escriben pueden generar varios tipos de excepciones potenciales: Podr´ıa emitir un comando para leer un archivo de un disco, pero el archivo no existe. Podr´ıa intentar escribir datos a un disco, pero el disco est´a lleno o no formateado. Podr´ıa pedir entrada al usuario, pero el usuario ingresa un tipo de dato no v´alido. 1
Podr´ıa intentar acceder un arreglo con un sub´ındice que no es v´alido. Estos errores son llamados excepciones porque no son ocurrencias usuales. El manejo de excepciones es el nombre para las t´ecnicas orientadas al objeto que manejan tales errores. Excepciones no planeadas que ocurren durante la ejecuci´on del programa son tambi´en llamadas excepciones en tiempo de ejecuci´ on, en contraste con errores de sintaxis que son descubiertos durante la compilaci´on del programa. Java incluye dos clases b´ asicas de errores: Error y Exception. Ambas clases descienden de la clase Throwable, como se muestra enseguida. Como todas las otras clases en Java, Error y Exception originalmente descienden de Object. java.lang.Object | +--java.lang.Throwable | +--java.lang.Exception | | | +--java.io.IOException | | | +--java.lang.RuntimeException | | | | | +--java.lang.ArithmeticException | | | | | +--java.lang.IndexOutOfBoundsException | | | | | | | +--java.lang.ArrayIndexOutOfBoundsException | | | | | +--java.util.NoSuchElementException | | | | | | | +--java.util.InputMismatchException | | | | | +--Otras... | | | +--Otras... | +--java.lang.Error | +--java.lang.VirtualMachineError | | | +--java.lang.OutOfMemoryError | | | +--java.lang.InternalError | | | +--Otras... | +--Otras... 2
La clase Error representa los errores m´as serios de los cuales el programa usualmente no se pueden recuperar. Podr´ıa haber memoria insuficiente para ejecutar un programa. Usualmente, no se usan o implementan objetos Error en el programa. Un programa no se puede recuperar de las condiciones Error por su propia cuenta. La clase Exception comprende errores menos serios que representan condiciones no usuales que surgen mientras un programa se ejecuta y del cual el programa se puede recuperar. Estos errores se dan al usar un sub´ındice de arreglo no v´alido o realizando ciertas operaciones aritm´eticas ilegales. Java muestra un mensaje Exception cuando el c´odigo del programa podr´ıa haber prevenido un error. La aplicaci´ on Division, c´ odigo 1, contiene en el m´etodo main() tres declaraciones de enteros, peticiones al usuario de valores para dos variables, y el c´alculo del valor del tercer entero haciendo la divisi´on de los valores dados. 1 2 3 4 5 6 7 8 9 10 11 12 13 14
import j a v a . u t i l . Scanner ; public c l a s s D i v i s i o n { public s t a t i c void main ( S t r i n g [ ] a r g s ) { Scanner e n t r a d a = new Scanner ( System . i n ) ; int numerador , denominador , r e s u l t a d o ; System . out . p r i n t ( ” I n g r e s a r e l numerador >> ” ) ; numerador = e n t r a d a . n e x t I n t ( ) ; System . out . p r i n t ( ” I n g r e s a r e l denominador >> ” ) ; denominador = e n t r a d a . n e x t I n t ( ) ; r e s u l t a d o = numerador / denominador ; System . out . p r i n t l n ( numerador + ” / ” + denominador + ” = ” + resultado ); } }
C´ odigo 1: La clase Division. Como se muestra en la siguiente salida, al ejecutar la aplicaci´on Division y habiendo el usuario ingresado un cero para el denominador, se genera un mensaje de excepci´on. Java no permite divisi´ on entera por cero, pero la divisi´ on flotante por cero es permitida, dando el resultado Infinity. Los programadores dicen que el programa experiment´o un choque, para indicar que el programa termin´o prematuramente con un error. El t´ermino “choque” quiz´as evolucion´o del error de hardware que ocurre cuando una cabeza de lectura/escritura abruptamente hace contacto con un disco duro, para incluir los errores de software que causan la falla del programa. $ java Division Ingresar el numerador >> 3 Ingresar el denominador >> 0 Exception in thread "main" java.lang.ArithmeticException: / by zero at Division.main(Division.java:10) De la salida de la aplicaci´ on, mostrada previamente, la Exception es una java.lang.ArithmeticException la cual es una de muchas subclases de Exception. Java conoce m´as de 75 categor´ıas de Exception con nombres raros como ActivationException, AlreadyBoundException, AWTException, CloneNotSupportedException, PropertyVetoException, y UnsupportedFlavorException.
3
Adem´as del tipo de Exception que se muestra en la ejecuci´on se muestra alguna informaci´on del error (“/ by zero”), el m´etodo que genero el error(Division.main), y el archivo y n´ umero de l´ınea para el error(Division.java, l´ınea 10). En la siguiente salida se muestran dos ejecuciones m´as de la aplicaci´on Division. En cada ejecuci´ on, el usuario ha metido datos no enteros para el denominador, primero una cadena de carateres, y luego un valor de punto flotante. De los mensajes de error se ve que en ambos casos la Exception es una InputMismatchException. La u ´ltima l´ınea del mensaje indica que el problema ocurri´o en la l´ınea 9 de la aplicaci´ on, y el mensaje de error que le antecede muestra que el problema ocurri´o dentro de la llamada a nextInt(). Como el usuario no ingreso un entero, el m´etodo nextInt() fall´o. Por supuesto que no se quiere modificar el m´etodo nextInt() que reside en la clase Scanner, se prefiere volver a ejecutar el programa y meter un entero, o modificar el programa para que estos errores no puedan ocurrir. $ java Division Ingresar el numerador >> 12 Ingresar el denominador >> tres Exception in thread "main" java.util.InputMismatchException at java.util.Scanner.throwFor(Scanner.java:909) at java.util.Scanner.next(Scanner.java:1530) at java.util.Scanner.nextInt(Scanner.java:2160) at java.util.Scanner.nextInt(Scanner.java:2119) at Division.main(Division.java:9) $ java Division Ingresar el numerador >> 12 Ingresar el denominador >> 3.0 Exception in thread "main" java.util.InputMismatchException at java.util.Scanner.throwFor(Scanner.java:909) at java.util.Scanner.next(Scanner.java:1530) at java.util.Scanner.nextInt(Scanner.java:2160) at java.util.Scanner.nextInt(Scanner.java:2119) at Division.main(Division.java:9) La lista de mensajes de error que se muestran en los intentos de ejecuci´on mostrados previamente es llamada una lista del historial de seguimiento de la pila, o un seguimiento de la pila. La lista muestra cada m´etodo que fue llamado conforme el programa se ejecutaba. S´olo porque una excepci´ on ocurra, no es obligatorio manejarla. Se puede dejar el c´odigo sin modificaci´on pero, la terminaci´ on del programa es abrupta. Posiblemente alg´ un usuario podr´ıa estar molesto si el programa termina abruptamente. Sin embargo, si el programa es usado para tareas de misi´on cr´ıtica tales como el control del tr´afico a´ereo, o para monitorear los signos vitales de un paciente en una cirug´ıa, una conclusi´ on abrupta podr´ıa ser desastrosa. Las t´ecnicas de manejo de error orientadas al objeto dan soluciones elegantes y seguras para errores. Se pueden escribir programas sin usar t´ecnicas de manejo de excepciones. Posiblemente la soluci´ on de manejo de errores m´ as com´ un ha sido el uso de una decisi´on para evitar un error. Por ejemplo, se puede cambiar el m´etodo main() de la clase Division para evitar dividir por cero agregando la 4
decisi´on como se muestra en la aplicaci´on DivisionIf, c´odigo 2. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
import j a v a . u t i l . Scanner ; public c l a s s D i v i s i o n I f { public s t a t i c void main ( S t r i n g [ ] a r g s ) { Scanner e n t r a d a = new Scanner ( System . i n ) ; int numerador , denominador , r e s u l t a d o ; System . out . p r i n t ( ” I n g r e s a r e l numerador >> ” ) ; numerador = e n t r a d a . n e x t I n t ( ) ; System . out . p r i n t ( ” I n g r e s a r e l denominador >> ” ) ; denominador = e n t r a d a . n e x t I n t ( ) ; i f ( denominador == 0 ) System . out . p r i n t l n ( ”No s e puede d i v i d i r por 0 ” ) ; else { r e s u l t a d o = numerador / denominador ; System . out . p r i n t l n ( numerador + ” / ” + denominador + ” = ” + resultado ); } } }
C´ odigo 2: Aplicaci´on DivisionIf. La aplicaci´on DivisionIf muestra un mensaje al usuario cuando cero es ingresado para el valor del denominador, pero no se puede recuperar cuando un dato no entero, como una cadena o un valor punto flotante, es ingresado. Los programas que pueden manejar excepciones apropiadamente se dice que son m´as tolerantes a las fallas y robustos. Las aplicaciones tolerantes a las fallas son dise˜ nadas para que puedan continuar operando, posiblemente a un nivel reducido, cuando alguna parte del sistema falla. La robustez representa el grado en el cual un sistema es resistente al uso, manteniendo el funcionamiento correcto. Es importante entender las t´ecnicas de manejo de excepciones porque los m´etodod incorporados en Java lanzar´an excepciones a los programas.
2.
Intentar c´ odigo y capturar excepciones
En la terminolog´ıa orientada al objeto, se “intenta (try)” un procedimiento que podr´ıa causar un error. Un m´etodo que detecta una condici´on de error “lanza (throws) una excepci´on”, y el c´ odigo de bloque que procesa el error “atrapa (catch) la excepci´on”. Cuando se crea un segmento de c´ odigo en el cual algo podr´ıa ir mal, se coloca el c´odigo en un bloque try, el cual es un bloque de c´odigo que se intenta ejecutar mientras se reconoce que una excepci´on podr´ıa ocurrir. Un bloque try consiste de los siguientes elementos: La palabra reservada try. Una llave de apertura. Sentencia ejecutables, incluyendo algunas que podr´ıan causar excepciones. 5
Una llave de cierre. Para manejar una excepci´ on lanzada, se pueden codificar uno o m´as bloques catch siguiendo inmediatamente al bloque try. Un bloque catch es un segmento de c´odigo que puede manejar una excepci´ on que podr´ıa ser lanzada por el bloque try que le precede. La excepci´on podr´ıa ser una que es lanzada autom´ aticamente, o se podr´ıa expl´ıcitamente escribir una sentencia throw. Una sentencia throw es una que manda un objeto Exception fuera de un bloque o un m´etodo para que pueda ser manejada donde sea. Una Exception lanzada puede ser capturada por un bloque catch. Cada bloque catch puede “atrapar” un tipo de excepci´on, es decir, un objeto que es del tipo Exception o de sus clases hijas. Se crea un bloque catch poniendo los siguientes elementos: La palabra reservada catch. Un par´entesis de apertura. Un tipo o subtipo Exception. Un identificador para una instancia del tipo Exception. Un par´entesis de cierre. Una llave de apertura. Las sentencias que toman la acci´ on que se quiere usar para manejar la condici´on de error. Una llave de cierre. Se muestra enseguida el formato general de un m´etodo que incluye un par try...catch. Un bloque catch se parece a un m´etodo llamado catch() que toma un argumento que es alg´ un tipo de Exception. Sin embargo, no es un m´etodo; no tiene tipo que se devuelve, y no se puede llamar directamente. Algunos programadores se refieren a un bloque catch como una “cl´ausula catch”. tipoDevuelto nombreM´ etodo(par´ ametros opcionales) { // sentencias opcionales previas al c´ odigo que se intentar´ a try { // sentencias que podr´ ıan generar una excepci´ on } catch (Exception algunaExcepci´ on) { // acciones que se har´ an si la excepci´ on ocurre } // sentencias opcionales que ocurren despu´ es del try, // ya sea que el bloque catch se ejecute o no } Cuadro 1: Formato try...catch en un m´etodo. En el formato, algunaExcepci´ on representa un objeto de la clase Exception o alguna de sus subclases. Si una excepci´ on ocurre durante la ejecuci´on del bloque try, las sentencias en el bloque
6
catch se ejecutan. Si no ocurre una excepci´on dentro del bloque try, el bloque catch no se ejecuta. De cualquier forma, las sentencias que siguen el bloque catch se ejecutan normalmente. La aplicaci´on DivisionPorCeroAtrapada, c´odigo 3, mejora la clase Division. El m´etodo main() contiene un bloque try con c´ odigo que intenta dividir. Cuando una divisi´on entera ilegal es intentada, una ArithmeticException es autom´aticamente creada y el bloque catch es ejecutado. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
import j a v a . u t i l . Scanner ; public c l a s s D i v i s i o n P o r C e r o A t r a p a d a { public s t a t i c void main ( S t r i n g [ ] a r g s ) { Scanner e n t r a d a = new Scanner ( System . i n ) ; int numerador , denominador , r e s u l t a d o ; System . out . p r i n t ( ” I n g r e s a r e l numerador numerador = e n t r a d a . n e x t I n t ( ) ; System . out . p r i n t ( ” I n g r e s a r e l denominador denominador = e n t r a d a . n e x t I n t ( ) ; try { r e s u l t a d o = numerador / denominador ; System . out . p r i n t l n ( numerador + ” / ” + ” = ” + resultado ); } catch ( A r i t h m e t i c E x c e p t i o n e r r o r ) { System . out . p r i n t l n ( ” I n t e n t o de d i v i d i r } } }
>> ” ) ; >> ” ) ;
denominador +
por 0 ” ) ;
C´ odigo 3: Aplicaci´on DivisionPorCeroAtrapada. Enseguida se muestra la salida de dos ejecuciones de la aplicaci´on, una con una excepci´on generada y una sin excepci´ on. $ java DivisionPorCeroAtrapada Ingresar el numerador >> 20 Ingresar el denominador >> 5 20 / 5 = 4 $ java DivisionPorCeroAtrapada Ingresar el numerador >> 20 Ingresar el denominador >> 0 Intento de dividir por 0 Nota. En la aplicaci´ on DivisionPorCeroCapturada, las operaciones lanzar y atrapar est´an en el mismo m´etodo. Despu´es se revisar´a que los lanzamientos y su correspondiente bloque de captura frecuentemente est´an en m´etodos separados.
Nota. Si se quiere mandar mensajes de error a una localidad diferente a la salida “normal”, se puede usar System.err en vez de System.out. Por ejemplo, si una aplicaci´on escribe un reporte a un archivo de disco espec´ıfico, se podr´ıa querer que los errores se escriben en una localidad diferente, como en otro disco o a la pantalla.
7
Cualquier ArithmeticException generada dentro del bloque try podr´ıa ser atrapada por el bloque catch en el m´etodo, para la aplicaci´ on DivisionPorCeroAtrapada. Se puede usar el m´etodo de instancia getMessage() calificado con la excepci´on atrapada, que ArithmeticException hereda de la clase Throwable, en vez de poner su propio mensaje. En la aplicaci´ on DivisionPorCeroAtrapada2, c´odigo 4, usa el m´etodo getMessage(), l´ınea 16, para generar el mensaje que “viene con” el argumento atrapado ArithmeticException para el bloque catch. Al ejecutar esta aplicaci´on con un denominador que sea cero, se muestra el mensaje “/ by zero”. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
import j a v a . u t i l . Scanner ; public c l a s s D i v i s i o n P o r C e r o A t r a p a d a { public s t a t i c void main ( S t r i n g [ ] a r g s ) { Scanner e n t r a d a = new Scanner ( System . i n ) ; int numerador , denominador , r e s u l t a d o ; System . out . p r i n t ( ” I n g r e s a r e l numerador numerador = e n t r a d a . n e x t I n t ( ) ; System . out . p r i n t ( ” I n g r e s a r e l denominador denominador = e n t r a d a . n e x t I n t ( ) ; try { r e s u l t a d o = numerador / denominador ; System . out . p r i n t l n ( numerador + ” / ” + ” = ” + resultado ); } catch ( A r i t h m e t i c E x c e p t i o n e r r o r ) { System . out . p r i n t l n ( ” I n t e n t o de d i v i d i r } } }
>> ” ) ; >> ” ) ;
denominador +
por 0 ” ) ;
C´ odigo 4: Aplicacion DivisionPorCeroAtrapada2. El mensaje “/ by zero” ya hab´ıa sido visto previamente, y fue cuando el programa, Division, no ten´ıa manejo de excepciones. Por supuesto que se quiere hacer m´ as que mostrar un mensaje de error en el bloque catch; despu´es de todo, Java lo hac´ıa sin requerir escribir c´odigo para atrapar cualquier excepci´on. Se podr´ıa querer agregar c´odigo para corregir el error; tal c´odigo podr´ıa forzar la aritm´etica al dividir por uno en vez de cero. Enseguida se muestra un c´odigo try...catch que realiza lo comentado. Despu´es del bloque catch, la aplicaci´ on podr´ıa continuar con la garant´ıa que resultado tiene un valor v´ alido, a´ un si la divisi´ on funcion´ o en el bloque try y el bloque catch no se ejecuto, o el bloque catch arregla el error. try { resultado = numerador / denominador; } catch (ArithmeticException error) { resultado = numerador / 1; } // el programa contin´ ua aqu´ ı; resultado est´ a garantizado // para tener un valor valido 8
Uso de un bloque try para hacer programa infalibles Uno de los usos m´ as comunes del bloque try es para esquivar errores de entrada de datos del usuario. Como cuando el usuario en la aplicaci´on Division ingresa un car´acter o un n´ umero de punto flotante en respuesta a la llamada del m´etodo nextInt() y hace que la aplicaci´on choque. Usando un bloque try se puede permitir manejar excepciones potenciales de conversi´on de datos causadas por usuarios distraidos. Las llamadas a nextInt() o nextDouble() se ponen en el bloque try y luego se maneja cualquier error generado. Despu´es de cualquier llamada al m´etodo next(), nextInt(), o nextDouble() se agrega una llamada a nextLine() para leer la tecla Intro remanente en el b´ ufer de entrada, antes de llamadas subsecuentes nextLine(). Cuando se intenta convertir a dato num´erico en un bloque try y es seguido por otro intento de conversi´ on, se debe tener en cuenta los caracteres remanentes dejados en el b´ ufer de entrada. En la aplicaci´ on IngresoEnteros, c´odigo 5, se aceptan y se muestran un arreglo de seis enteros. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
import j a v a . u t i l . Scanner ; public c l a s s I n g r e s o E n t e r o s { public s t a t i c void main ( S t r i n g [ ] a r g s ) { int [ ] l i s t a N u m e r o s = { 0 , 0 , 0 , 0 , 0 , 0 } ; Scanner e n t r a d a = new Scanner ( System . i n ) ; f o r ( int x = 0 ; x < l i s t a N u m e r o s . l e n g t h ; ++x ) { try { System . out . p r i n t ( ” I n g r e s a un e n t e r o >> ” ) ; listaNumeros [ x ] = entrada . nextInt ( ) ; } catch ( E x c e p t i o n e ) { System . out . p r i n t l n ( ” O c u r r i ´o una e x c e p c i ´on” ) ; } // e n t r a d a . n e x t L i n e ( ) ; } System . out . p r i n t ( ” Los n´ u meros son : ” ) ; f o r ( int num : l i s t a N u m e r o s ) System . out . p r i n t (num + ” ” ) ; System . out . p r i n t l n ( ) ; } }
C´ odigo 5: Aplicaci´on IngresoEnteros. Al ejecutar la aplicaci´ on IngresoEnteros y poner un dato v´alido para el primer n´ umero, se obtiene la siguiente salida, la cual s´ı muestra “Ocurri´o una excepci´on”, pero al usuario no se le permite ingresar datos para cualquiera de los n´ umeros restantes. El problema se puede corregir descomentando la llamada a nextLine(), l´ınea 14, del c´odigo anterior. De esta forma el mensaje de excepci´ on es puesto, y se permite al usuario continuar ingresando datos. $ java IngresoEnteros Ingresa un entero >> a Ocurri´ o una excepci´ on Ingresa un entero >> Ocurri´ o una excepci´ on Ingresa un entero >> Ocurri´ o una excepci´ on 9
Ingresa un entero >> Ingresa un entero >> Ingresa un entero >> Los n´ umeros son: 0 0
Ocurri´ o una excepci´ on Ocurri´ o una excepci´ on Ocurri´ o una excepci´ on 0 0 0 0
Actividad 1. Descomentar la l´ınea 14 de la aplicaci´on IngresoEnteros, compilar y ejecutar la apliaci´on meter un dato no v´ alido para observar que indica el error
Declaraci´ on e inicializaci´ on de variables en bloques try...catch Se puede incluir cualquier sentencia legal dentro de un bloque try o catch, incluyendo la declaraci´ on de variables. Una variable declarada dentro de un bloque es local a ese bloque, por lo que esta variable sirve s´ olo para un prop´ osito temporal. Si se quiere usar una variable tanto en un bloque try, como en un catch y despu´es, entonces se debe declarar la variable antes de que el bloque try inicie. Si el valor inicial a esta variable se asignar´a dentro del bloque try...catch, se debe tener cuidado que la variable reciba un valor u ´til; de otra forma, cuando se use la variable despu´es del bloque try...catch, el programa no compilar´a. En el programa PruebaVariableNoInicializada, c´odigo 6, x est´a declarada y su valor es asignado por la entrada del usuario en el bloque try...catch. Como el usuario podr´ıa no ingresar un entero, la conversi´on podr´ıa fallar, y una excepci´on podr´ıa ser lanzada. En este c´odigo el bloque catch s´ olo muestra un mensaje y no asigna un valor a x. Cuando el programa intenta mostrar x despu´es del bloque catch, un mensaje de error es generado. Actividad 2. Compilar el c´ odigo PruebaVariableNoInicializada para ver el mensaje de error. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
import j a v a . u t i l . Scanner ; public c l a s s P r u e b a V a r i a b l e N o I n i c i a l i z a d a { public s t a t i c void main ( S t r i n g [ ] a r g s ) { int x ; Scanner e n t r a d a = new Scanner ( System . i n ) ; try { System . out . p r i n t ( ” I n g r e s a un e n t e r o )))) ) ” ; x = entrada . nextInt ( ) ; } catch ( E x c e p t i o n e ) { System . out . p r i n t l n ( ” O c u r r i ´o e x c e p c i ´on” ) ; } System . out . p r i n t l n ( ”x e s ” + x ) ; } }
C´ odigo 6: Aplicaci´on PruebaVariableNoInicializada. Se tienen tres opciones para arreglar este error: Se puede asignar un valor a x antes de que el bloque try inicie. De esta forma, a´ un si una excepci´ on es lanzada, x tendr´ a un valor usable para mostrar en la u ´ltima sentencia. 10
Se puede asignar un valor usable a x dentro del bloque catch. De esta forma, si una excepci´ on es lanzada, x tendr´ a otra vez un valor usable. Se puede mover la sentencia de salida al bloque try para que en el caso de que el usuario ingrese un entero muestre ese valor, y si la conversi´on falla entonces se deja el bloque try, para ser ejecutado el bloque catch mostrando el mensaje de error y sin usar x. Actividad 3. Modificar la aplicaci´ on DivisionPorCeroAtrapada que los enteros sean solicitados con cuadros de di´ alogo de entrada y el m´etodo Integer.ParseInt() para convertir las cadenas devueltas por los cuadros de di´ alogo de entrada en enteros. De igual forma modificar el bloque catch para mostrar el error con un cuadro de di´alogo.
3.
Lanzar y atrapar excepciones m´ ultiples
Se pueden poner tantas sentencias como se requiera dentro de un bloque try, y se pueden atrapar tantas excepciones como se quiera. Si se intenta m´as de una sentencia, s´olo la primera sentencia generadora de error lanza una excepci´ on. Tan pronto la excepci´on ocurre, la l´ogica se transfiere al bloque catch, lo cual deja el resto de las sentencias en el bloque try sin ejecutar. Cuando un programa contiene varios bloques catch, estos son examinados en secuencia hasta que un apareamiento es encontrado para el tipo de excepci´on que ha ocurrido. Entonces, el bloque catch apareado se ejecuta, y cada bloque catch restante es omitido. Considerar la aplicaci´ on DivisionPorCeroAtrapada3, c´odigo 7, donde el m´etodo main() lanza dos tipos de objetos Exception: una ArithmeticException y una InputMismatchException. El bloque try en la aplicaci´ on rodea todas las sentencias en la cual las excepciones podr´ıan ocurrir.
11
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
import j a v a . u t i l . ∗ ; public c l a s s D i v i s i o n P o r C e r o A t r a p a d a 3 { public s t a t i c void main ( S t r i n g [ ] a r g s ) { Scanner e n t r a d a = new Scanner ( System . i n ) ; int numerador , denominador , r e s u l t a d o ; try { System . out . p r i n t ( ” I n g r e s a r e l numerador >> ” ) ; numerador = e n t r a d a . n e x t I n t ( ) ; System . out . p r i n t ( ” I n g r e s a r e l denominador >> ” ) ; denominador = e n t r a d a . n e x t I n t ( ) ; r e s u l t a d o = numerador / denominador ; System . out . p r i n t l n ( numerador + ” / ” + denominador + ” = ” + resultado ); } catch ( E x c e p t i o n e ) { System . out . p r i n t l n ( e . getMessage ( ) ) ; } catch ( A r i t h m e t i c E x c e p t i o n e r r o r ) { System . out . p r i n t l n ( e r r o r . getMessage ( ) ) ; } catch ( InputMismatchException e r r o r ) { System . out . p r i n t l n ( ” Tipo de dato i n c o r r e c t o ” ) ; } } }
C´ odigo 7: Aplicaci´on DivisionPorCeroAtrapada3. Nota. La aplicaci´ on DivisionPorCeroAtrapada3 debe importar la clase java.util.InputMismatchException para poder usar un objeto InputMismatchException. El paquete java.util es tambi´en ocupado por la clase Scanner, as´ı que es m´as f´acil importar el paquete completo. Nota. Si se usa el m´etodo getMessage() con el objeto InputMismatchException, se ver´a que el mensaje es null, porque null es el valor del mensaje por defecto para un objeto InputMismatchException.
Para la aplicaci´ on DivisionPorCeroAtrapada3 el bloque try se ejecuta y varias salidas son posibles: Si el usuario ingresa dos enteros usables, resultado es calculado, la salida normal es mostrada, y ning´ un bloque catch se ejecuta. Si el usuario ingresa un valor no v´alido ya sea en la l´ınea 7, o en la l´ınea 9, un objeto InputMismatchException es creado y lanzado. Cuando el programa encuentra el primer bloque catch el bloque es pasado porque la excepci´on generada InputMismatchException no empata con ArithmeticException. Al encontrar el programa el segundo bloque catch, el tipo empata, y el mensaje “Tipo de dato incorrecto” es mostrado. Si el usuario ingresa cero para el denominador, la sentencia de divisi´on lanza una ArithmeticException, y el bloque try es abandonado. Cuando el programa encuentra el primer bloque catch, el tipo de excepci´ on empata, el valor del m´etodo getMessage() es mostrado, y luego el segundo bloque catch es pasado. 12
Cuando se ponen varios bloques catch siguiendo un bloque try, se debe tener cuidado de que algunos bloques catch no se hagan inalcanzables. Por ejemplo, si un primer bloque catch atrapan una ArithmeticException y enseguida otra atrapa una Exception, los errores ArithmeticException causan que el primer bloque catch se ejecute y otros tipos que deriven de Exception “caigan” al bloque catch Exception m´ as general. Por otra parte, si se invierte la secuencia de los bloques catch para que atrape primero el bloque catch Exception, a´ un las ArithmeticException ser´ an atrapadas por el primer catch, y de esta forma el bloque catch ArithmeticException ser´a inalcanzable ya que la ArithmeticException ser´a atrapada por el catch Exception, y por lo tanto la clase no compilar´ a. Se deben de poner los bloques catch de forma que los m´as especializados est´en primero y al final los m´ as generales. Es decir, cada excepci´on deber´a caer a trav´es de tantos bloques catch como sea necesario hasta llegar con el que lo procesar´a. Nota. Otras sentencias que son inalcanzables son aquellas que siguen despu´es de una sentencia return en un m´etodo. Crear bloques catch inalcanzables provoca un error del compilador que genera un mensaje indicando que la excepci´on “has already been caught” (ya ha sido atrapada).
En ocasiones se quiere ejecutar el mismo c´odigo no importando cual tipo de excepci´on ocurre. En la aplicaci´on DivisionPorCeroAtrapada3, c´odigo 7, cada uno de los dos bloques catch muestran un mensaje u ´nico, pero se podr´ıa querer que ambos bloques catch muestren el mismo mensaje. Como ArithmeticException e InputMismatchException son subclases de Exception, se puede reescribir el c´ odigo 7, como se muestra en el c´odigo 8, usando un bloque catch gen´erico, l´ıneas 15—17, que puede atrapar cualquier tipo de objeto Exception. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
import j a v a . u t i l . ∗ ; public c l a s s D i v i s i o n P o r C e r o A t r a p a d a 4 { public s t a t i c void main ( S t r i n g [ ] a r g s ) { Scanner e n t r a d a = new Scanner ( System . i n ) ; int numerador , denominador , r e s u l t a d o ; try { System . out . p r i n t ( ” I n g r e s a r e l numerador >> ” ) ; numerador = e n t r a d a . n e x t I n t ( ) ; System . out . p r i n t ( ” I n g r e s a r e l denominador >> ” ) ; denominador = e n t r a d a . n e x t I n t ( ) ; r e s u l t a d o = numerador / denominador ; System . out . p r i n t l n ( numerador + ” / ” + denominador + ” = ” + resultado ); } catch ( E x c e p t i o n e r r o r ) { System . out . p r i n t l n ( ” O p e r a c i ´on no e x i t o s a ” ) ; } } }
C´ odigo 8: Aplicaci´on DivisionPorCeroAtrapada4. El bloque catch gen´erico de la clase DivisionPorCeroAtrapada4 acepta un tipo de argumento Exception gen´erico lanzado por cualquier sentencia potencialmente causante del error, as´ı el bloque gen´erico act´ ua como un bloque “atrapa todo”. Cuando un error aritm´etico o de entrada ocurre, la excepci´on lanzada es “promovida” a un error Exception en el bloque catch. Actividad 4. Ejecutar la aplicaci´ on DivisionPorCeroAtrapada4 para ver que no importando el 13
tipo de error que ocurra durante la ejecuci´on del programa, el mensaje general “Operaci´on no exitosa” es mostrado Como una caracter´ıstica nueva en Java 7, un bloque catch ya puede ser escrito para atrapar m´ ultiples tipos de excepciones espec´ıficas. Por ejemplo, el siguiente bloque catch atrapa dos tipos de excepciones. Cuando cualquiera es atrapada, su identificador local es e. catch (ArithmeticException, InputMismatchException e) { ... } Un m´etodo puede lanzar cualquier cantidad de tipos de excepciones, sin embargo varios programadores consideran que es un estilo pobre para un m´etodo lanzar y atrapar m´as de tres o cuatros tipos. Si lo hace, una de las siguientes condiciones podr´ıa ser cierta: Quiz´as el m´etodo est´ a intentando realizar muchas tareas diversas y entonces deber´ıa ser dividido en m´etodos m´ as peque˜ nos. Quiz´as los tipos de excepci´ on lanzados son muy espec´ıficos y deber´ıan ser generalizados, como se hizo en la aplicaci´ on DivisionPorCeroAtrapada4, c´odigo 8. Actividad 5. Usar la actividad 2 para agregar un bloque catch que atrape un objeto NumberFormatException. Si este bloque se ejecuta, mostrar un mensaje de error, asignar a numerador y denominador el valor por defecto 999, y forzar resultado a uno. Ejecutar la aplicaci´on para confirmar que el programa trabaja apropiadamente si se dan dos enteros usables, un cero no usable para el segundo entero, o datos no enteros como cadenas conteniendo caracteres alfab´eticos o de puntuaci´ on.
4.
Bloque finally
Cuando se tienen acciones que se deben hacer al final de una secuencia try...catch, se puede usar un bloque finally. El c´ odigo dentro de un bloque finally se ejecuta no importando si el bloque try precedente identifica una excepci´on. Generalmente, el bloque finally se usa para realizar tareas de limpieza que deben suceder no importando si alguna excepci´on ocurri´o o no, y si cualquier excepci´ on que ocurri´ o fue capturada o no. El siguiente formato muestra una secuencia try....catch que usa un bloque finally. try { // sentencias a } catch (Exception e) // acciones que } finally { // acciones que }
intentar { ocurren si Exception fue lanzada
ocurren si el bloque catch fue ejecutado o no
14
En una secuencia try...catch que no incluya un bloque finally, ver el formato del cuadro 1, las sentencias opcionales que ocurren despu´es del try, al final del m´etodo, podr´ıan nunca ser ejecutadas por al menos dos razones. Cualquier bloque try podr´ıa lanzar un objeto Exception para el cual no se proporcione un bloque catch. En el caso de una excepci´on no manejada, la ejecuci´on del programa termina inmediatamente, la excepci´ on es mandada al sistema operativo para que la maneje, y el m´etodo actual es abandonado. El bloque try o catch podr´ıan contener la sentencia System.exit();, la cual detiene la ejecuci´ on inmediatamente. Cuando se incluye un bloque finally, se asegura que las sentencias finally se ejecutar´an antes que el m´etodo sea abandonado, a´ un si el m´etodo concluye prematuramente. Los programadores usan un bloque finally cuando el programa usa archivos de datos que deban cerrarse. El formato del cuadro representa parte de la l´ ogica para un programa que maneja archivos. try { // Abrir el archivo // Leer el archivo // Colocar los datos del archivo en un arreglo // Calcular el promedio de los datos // Mostrar el promedio } catch (IOException e) { // mostrar un mensaje de error // salir de la aplicaci´ on } finally { // Si el archivo est´ a abierto entonces cerrarlo. } Cuadro 2: Pseudoc´ odigo que intenta leer un archivo y manejar una excepci´on IOException El pseudoc´odigo del cuadro 2 representa una aplicaci´on que abre un archivo; en Java, si un archivo no existe cuando se abre, una excepci´on entrada/salida, o IOException, es lanzada y el bloque catch puede manejar el error. Como el pseudoc´odigo usa un arreglo, una IndexOutOfBoundsException no atrapada podr´ıa ocurrir a´ un si se pudo abrir el archivo, esta excepci´on ocurre cuando un sub´ındice no est´ a en rango de sub´ındices v´alidos. Tambi´en podr´ıa dividirse por cero y una ArithmeticException no atrapada podr´ıa ocurrir. En cualquiera de estos eventos, se podr`ıa querer cerrar el archivo antes de proceder. Usando el bloque finally, se asegura que el archivo ser´a cerrado porque el c´odigo en este bloque se ejecuta antes que el control regrese al sistema operativo. El c´ odigo en el bloque finally se ejecuta no importando cual de las siguientes salidas del bloque try ocurra: El bloque try termina normalmente. El bloque catch se ejecuta. 15
Una excepci´ on no capturada causa que el m´etodo sea abandonado prematuramente. Una excepci´ on no capturada no permite que el bloque try concluya, ni causa que un bloque catch sea ejecutado. Si una aplicaci´ on pudiera lanzar varios tipos de excepciones, se puede intentar alg´ un c´odigo, atrapar la posible excepci´ on, intentar alg´ un c´ odigo m´as y atrapar la excepci´on, y as´ı sucesivamente. La mejor alternativa es intentar todas las sentencias que podr´ıan lanzar excepciones, y luego incluir todos los bloques catch necesarios y un bloque finally opcional. Esta es la alternativa mostrada en el cuadro 2, y resulta en un l´ ogica que es m´as f´acil de entender. Se puede evitar usar un bloque finally, pero se podr´ıa necesitar repetir c´odigo. Para el pseudoc´odigo del cuadro 2 para evitar usar el bloque finally se podr´ıa insertar la sentencia “Si el archivo est´a abierto entonces cerrrarlo” como la u ´ltima sentencia en el bloque try y despu´es de mostrar el mensaje de error en el bloque catch. Sin embargo, escribiendo c´odigo s´olo una vez en el bloque finally es m´ as simple y menos susceptible a error. Nota. Si un bloque try llama al m´etodo System.exit() y el bloque finally llama al mismo m´etodo, el m´etodo exit() en el bloque finally se ejecuta. La llamada del m´etodo exit() del bloque try es abandonado.
5.
Ventajas del manejo de excepci´ on
Antes de la llegada de los lenguajes orientados al objeto, los errores potenciales del programa fueron manejados usando algo enredado, m´etodos propensos a error. Un programa procedural tradicional no orientado al objeto podr´ıa realizar tres m´etodos que dependen de otros usando c´odigo que proporcione revision similar al pseudoc´odigo del cuadro 3. llamar al metodoA() if metodoA() trabaj´ o { llamar al metodoB() if metodoB() trabaj´ o { llamar al metodoC() if metodoC() trabaj´ o todo est´ a bien, as´ ı que mostrar resultadoFinal else poner c´ odigoError a ’C’ } else poner c´ odigoError a ’B’ } else poner c´ odigoError a ’A’ Cuadro 3: Pseudoc´ odigo representando revisi´on tradicional de error
16
El pseudoc´odigo del cuadro 2 representa una aplicaci´on en la cual la l´ogica deber´a pasar tres pruebas antes de que resultadoFinal pueda ser mostrado. El programa ejecuta el metodoA(); luego llama al metodoB() s´ olo si metodoA() es exitoso. De igual forma, el metodoC() se ejecuta solo cuando ambos m´etodos metodoA() y metodoB() son ambos exitosos. Cuando falla alguno de los m´etodos, el programa pone un c´odigo ´ A’, ’B’, o ’C’ apropiado en c´ odigoError, quiz´ as c´ odigoError sea usado m´ as tarde en la aplicaci´on. La l´ogica es dif´ıcil de seguir, y el prop´ osito de la aplicaci´ on y la salida usual intentada, mostrar resultadoFinal, se pierde en laberinto de sentencias if. Tambi´en, se pueden f´ acilmente cometer errores de codificaci´on dentro del programa por el anidamiento complicado, sangrado, y apertura y cierre de llaves. Comparar la misma l´ ogica del programa usando la t´ecnica orientada al objeto, manejo de errores de Java mostrada en el cuadro 4. Usando la t´ecnica try...catch se logran los mismos resultados que el m´etodo tradicional, pero las sentencias del programa que hacen el trabajo “verdadero”, la llamada a los m´etodos A, B, y C y mostrar resultadoFinal, est´an puestos juntos, donde su l´ ogica es m´as f´acil de seguir. Los pasos try deber´ıan usualmente trabajar sin generar errores; despu´es de todo, los errores son “excepciones”. Es conveniente ver los pasos usuales en un solo lugar, y los eventos excepcionales est´ an agrupados y puestos fuera de la acci´on primaria. try { llamar al metodoA() llamar al metodoB() llamar al metodoC() } catch (error del metodoA()){ poner c´ odigoError a ’A’ } catch (error del metodoB()){ poner c´ odigoError a ’B’ } catch (error del metodoC()){ poner c´ odigoError a ’C’ } Cuadro 4: Pseudoc´ odigo representando el manejo de excepciones orientada al objeto Una ventaja del manejo de excepciones orientada al objeto, adem´as de la claridad, es la flexibilidad que permite en el manejo de situaciones de error. Cuando un m´etodo escrito lanza una excepci´ on, el mismo m´etodo puede atrapar la excepci´on, lo cual no es necesario y en la mayor´ıa de los programas no se hace. Generalmente no se quiere que un m´etodo maneje su propia excepci´on. En muchos casos, se quiere que el m´etodo revise los errores, pero no se quiere hacer que un m´etodo maneje un error si este lo encuentra. Otra ventaja con el manejo de excepciones es que se tiene la habilidad para manejar apropiadamente las excepciones conforme se decide como hacerlo. Cuando se escribe un m´etodo, este puede llamar otro, atrapar una excepci´on lanzada, y se puede decidir que se quiere hacer. Los programas pueden reaccionar a excepciones espec´ıficas para sus prop´ositos actuales. Los m´etodos son flexibles en parte porque son reusables. Cada aplicaci´on llamada podr´ıa necesitar manejar un error lanzado diferentemente, dependiendo de su prop´osito. Considerar una aplicaci´ on que usa un m´etodo que divide valores podr´ıa necesitar terminar si la divisi´on por cero ocurre. Un
17
programa diferente podr´ıa querer que el usuario reingrese el dato a ser usado, y un tercer programa podr´ıa querer forzar la divisi´ on por uno. El m´etodo que contiene la sentencia de divisi´on podr´ıa lanzar el error, pero cada programa que llama puede asumir la responsabilidad para manejar el error detectado por el m´etodo en una forma apropiada.
6.
Especificar excepciones que un m´ etodo puede lanzar
Si un m´etodo lanza una excepci´ on que no atrapar´a pero que ser´a atrapada por un m´etodo diferente, se debe usar la palabra reservada throws seguida por un tipo Exception en la cabecera del m´etodo. Esta pr´actica es conocida como especificaci´ on de excepci´ on. En la clase ListaPrecios, c´ odigo 9, usada por una empresa para tener una lista de precios de los art´ıculos que venden, hay solamente cuatro precios y un s´olo m´etodo que muestra el precio de un art´ıculo individual. El m´etodo mostrarPrecio() acepta un par´ametro que ser´a usado como el sub´ındice del arreglo, pero como el sub´ındice podr´ıa estar fuera de rango, el m´etodo contiene una cl´ausula throws en la l´ınea 3, reconociendo que podr´ıa lanzar una excepci´on. 1 2 3 4 5 6
public c l a s s L i s t a P r e c i o s { private s t a t i c f i n a l double [ ] p r e c i o = { 3 5 . 5 0 , 6 7 . 8 0 , 1 2 . 5 0 , 3 2 . 2 0 } ; public s t a t i c void m o s t r a r P r e c i o ( int a r t i c u l o ) throws IndexOutOfBoundsException { System . out . p r i n t l n ( ” El p r e c i o e s $ ” + p r e c i o [ a r t i c u l o ] ) ; } }
C´ odigo 9: La clase ListaPrecios. La aplicaci´on AplicacionListaPrecios1, c´odigo 10, se ha escogido manejar la excepci´on en el bloque catch, l´ıneas 11—13, para mostrar un precio de cero. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
import j a v a . u t i l . Scanner ; public c l a s s A p l i c a c i o n L i s t a P r e c i o s 1 { public s t a t i c void main ( S t r i n g [ ] a r g s ) { Scanner e n t r a d a = new Scanner ( System . i n ) ; int a r t i c u l o ; System . out . p r i n t ( ” I n g r e s a r e l n´ u mero d e l a r t´ıc u l o >> ” ) ; a r t i c u l o = entrada . nextInt ( ) ; try { ListaPrecios . mostrarPrecio ( a r t i c u l o ) ; } catch ( IndexOutOfBoundsException e r r o r ) { System . out . p r i n t l n ( ” El p r e c i o e s $0 ” ) ; } } }
C´ odigo 10: Clase AplicacionListaPrecios1. En la aplicaci´ on AplicacionListaPrecios2, c´odigo 11, el programador ha escogido manejar la excepci`on en el bloque catch, l´ıneas 12—14, mostrando el precio del u ´ltimo art´ıculo del arreglo.
18
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
import j a v a . u t i l . ∗ ; public c l a s s A p l i c a c i o n L i s t a P r e c i o s 2 { public s t a t i c void main ( S t r i n g [ ] a r g s ) { Scanner e n t r a d a = new Scanner ( System . i n ) ; int a r t i c u l o ; f i n a l int ARTMAX = 3 ; System . out . p r i n t ( ” I n g r e s a r e l n´ u mero d e l a r t´ıc u l o >> ” ) ; a r t i c u l o = entrada . nextInt ( ) ; try { ListaPrecios . mostrarPrecio ( a r t i c u l o ) ; } catch ( IndexOutOfBoundsException e r r o r ) { L i s t a P r e c i o s . m o s t r a r P r e c i o (ARTMAX) ; } } }
C´ odigo 11: Clase AplicacionListaPrecios2. Otros programadores escribiendo otras aplicaciones que usen la clase ListaPrecios podr´ıan escoger todav´ıa acciones diferentes, pero pueden usar el m´etodo flexible mostrarPrecio() porque este no limita la llamada de los m´etodos de recurso escogidos. Para la mayor´ıa de los m´etodos Java que se escriben no se usa la cl´ausula throws. La mayor´ıa del tiempo se le permite a Java manejar cualquier excepci´on abortando el programa, ya que de otra forma se requerir´ıa dar instrucciones para manejar cada error posible. Muchas excepciones nunca tendr´ an que ser expl´ıcitamente lanzadas o atrapadas, ni se tendr´an que incluir una cl´ausula throws en la cabecera de los m´etodos que autom´aticamente lanzan estas excepciones. Las u ´nicas excepciones que deber´ an ser atrapadas o nombradas en una cl´ausula throws son del tipo conocido como excepciones comprobadas. Las excepciones de Java pueden ser categorizadas en dos tipos: Excepciones no comprobadas. Estas heredan de la clase Error o RuntimeException. Se pueden manejar estas excepciones en los programas, pero no se est´a obligado a hacerlo. Por ejemplo, dividir por cero es un tipo de RuntimeException, y no se est´a obligado a manejar esta excepci´ on, se puede dejar que el programa termine. Excepciones comprobadas. Estas excepciones son del tipo donde los programadores podr´ıan anticipar y de las cuales los programas deber´ıan poderse recuperar. Todas las excepciones que expl´ıcitamente se lanzen y que desciendan de la clase Exception son excepciones comprobadas. Los programadores dicen que las excepciones comprobadas est´an sujetas al catch o especificamiento de requerimientos, lo cual significa que si se lanza una excepci´on comprobada desde un m´etodo, se debe hacer uno de los siguientes: Atrapar y manejar la excepci´ on posible. Declarar la excepci´ on en su cl´ ausula throws. El m´etodo llamado puede entonces relanzar la excepci´ on a otro m´etodo que podr´ıa atraparla o lanzarla otra vez. 19
Es decir, cuando una excepci´ on es una comprobada, los programas clientes son forzados a negociar con la posibilidad que una excepci´ on ser´a lanzada. Si se escribe un m´etodo que expl´ıcitamente lanza una excepci´on comprobada que no es capturada dentro del m´etodo, Java requiere que se use la cl´ausula throws en la cabecera del m´etodo. Usando la cl´ausula throws no significa que el m´etodo lanzar´ a una excepci´on, todo podr´ıa ir bien. En vez de esto significa que el m´etodo podr´ıa lanzar una excepci´on. Se incluye la cl´ausula throws en la cabecera del m´etodo para que las aplicaciones que usen los m´etodos est´en notificados del potencial de una excepci´ on. Nota. La firma del m´etodo es la combinaci´on del nombre del m´etodo y la cantidad, tipo y orden de los argumentos. La cl´ausula throws no es parte de la firma porque no se puede crear una clase que contenga m´etodos m´ ultiples que s´ olo difieran en sus cl´ausulas throws; el compilador considera que los m´etodos tienen firma id´entica. Se podr´ıa decir que la cl´ausula throws es parte de la interfaz del m´etodo. Nota. Un m´etodo que anule otro no puede lanzar una excepci´on a menos que esta lance el mismo tipo como su padre o una subclase del tipo lanzado de su padre. Estas reglas no aplican a m´etodos sobrecargados. Cualquier excepci´ on podr´ıa o no ser lanzada de una versi´on de un m´etodo sobrecargado sin considerar que excepciones son lanzadas por otras versiones de un m´etodo sobrecargado.
Para poder usar un m´etodo a su potencial completo, se debe conocer el nombre del m´etodo y tres piezas adicionales de informaci´ on: El tipo regresado por el m´etodo. El tipo y n´ umero de argumentos que el m´etodo requiere. El tipo y cantidad de excepciones que el m´etodo lanza. Para usar un m´etodo, se debe saber que tipos de argumentos son requeridos. Se puede llamar un m´etodo sin saber su tipo devuelto, pero no se puede beneficiar de alg´ un valor que el m´etodo devuelva, adem´ as no se entender´ıa el prop´osito del m´etodo. De igual forma no se pueden hacer decisiones coherentes acerca de que hacer en caso de un error si no se sabe que tipos de excepciones un m´etodo podr´ıa lanzar. Cuando un m´etodo podr´ıa lanzar m´ as de una tipo de excepci´on, se puede indicar una lista de las excepciones potenciales en la cabecera del m´etodo separ´andolas con comas. Como una alternativa, si todas las excepciones descienden del mismo padre, se puede especificar la clase padre m´as general. Si el m´etodo podr´ıa lanzar una ArithmeticException o una ArrayIndexOutOfBoundsException, se puede solo especificar que el m´etodo lanza una RuntimeException. Una ventaja de esta t´ecnica es cuando el m´etodo es modificado para incluir RuntimeException m´as espec´ıfico en el futuro, la cabecera del m´etodo no cambiar´ a. Esto ahorra tiempo para los usuarios de los m´etodos, quienes no tendr´an que modificar sus propios m´etodos para considerar nuevos tipos RuntimeException. Una alternativa extrema es especificar que el m´etodo lance un objeto Exception general, as´ı todas las excepciones est´ an incluidas en una cl´ausula. Con esto se simplifica la especificaci´on de la excepci´on, pero se disfraza la informaci´ on acerca de los tipos espec´ıficos de excepciones que podr´ıan ocurrir, y esta informaci´ on tiene valor para los usuarios de los m´etodos. 20
Nota. Se declaran solo excepciones comprobadas. Las excepciones tiempos de ejecuci´on pueden ocurrir en cualquier parte del programa, y pueden ser numerosas. Los programas podr´ıan ser menos limpios y m´as inc´ omodos si se tiene que tener en cuenta las excepciones tiempos de ejecuci´on en cada declaraci´ on del m´etodo. Por lo tanto, el compilador de Java no requiere que se atrape o se especifiquen excepciones tiempos de ejecuci´on.
7.
Traza de excepciones en la pila de llamadas
Cuando un m´etodo llama a otro, el sistema operativo de la computadora deber´a seguir la pista de donde provino la llamada del m´etodo, y el control del programa debe regresar al m´etodo llamador cuando el m´etodo llamado est´e completo. Si el m´ etodoA() llama al m´ etodoB(), el sistema operativo tiene que “recordar” regresar al m´ etodoA() cuando el m´ etodoB() termine. De igual forma si el m´ etodoB() llama al m´ etodoC(). La localidad de memoria conocida como la pila de llamadas es donde la computadora guarda la lista de la ubicaci´on del m´etodo al cual el sistema deba regresar. Cuando un m´etodo lanza una excepci´ on y el m´etodo no la atrapa, la excepci´on es lanzada al siguiente m´etodo de la pila de llamadas, o al m´etodo que llam´o al m´etodo ofendido. Si el m´ etodoA() llama al m´ etodoB(), y el m´ etodoB() llama al m´etodo m´ etodoC(), y el m´ etodoC() lanza una excepci´ on, Java primero busca por un bloque catch en el m´ etodoC(). Si ninguno existe, Java busca por la misma cosa en el m´ etodoB(). Si el m´ etodoB() no tiene un bloque catch, Java busca en m´ etodoA(). Si el m´ etodoA() no puede atrapar la excepci´on, esta es lanzada a la M´aquina Virtual Java, la cual muestra un mensaje en la pantalla. La aplicaci´on DemoTrazaPila, c´ odigo 12, en el m´etodo main() llama al m´ etodoA(), el cual muestra un mensaje y llama al m´ etodoB(). Dentro del m´ etodoB(), otro mensaje es mostrado y el m´ etodoC() es llamado. En el m´ etodoC(), otro mensaje es mostrado. Luego un arreglo de tres enteros es declarado, y el programa intenta mostrar el cuarto elemento del arreglo. El programa compila correctamente, pero un error es detectado cuando el m´ etodoC() intenta acceder el elemento fuera del rango.
21
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
public c l a s s DemoTrazaPila { public s t a t i c void main ( S t r i n g [ ] a r g s ) { m´etodoA ( ) ; } public s t a t i c void m´etodoA ( ) { System . out . p r i n t l n ( ”En e l m´etodoA ( ) ” ) ; m´etodoB ( ) ; } public s t a t i c void m´etodoB ( ) { System . out . p r i n t l n ( ”En e l m´e todoB ( ) ” ) ; m´etodoC ( ) ; } public s t a t i c void m´etodoC ( ) { System . out . p r i n t l n ( ”En e l m´etodoC ( ) ” ) ; int [ ] a r r e g l o = { 0 , 1 , 2 } ; System . out . p r i n t l n ( a r r e g l o [ 3 ] ) ; } }
C´ odigo 12: Clase DemoTrazaPila. Enseguida se muestra la salida cuando se ejecuta DemoTrazaPila que tiene tres mensajes, indicando el orden en el que fueron llamados los m´etodos m´ etodoA(), m´ etodoB() y m´ etodoC(). Sin embargo, cuando el m´ etodoC() intenta acceder el elemento fuera de rango en el arreglo, una excepci´on ArrayIndexOutOfBoundsException es autom´aticamente lanzada. El mensaje de error genera muestra que la excepci´ on ocurri´ o en la l´ınea 16 del archivo en el m´ etodoC(), el cual fue llamado en la l´ınea 11 del archivo por el m´ etodoB(), el cual fue llamado en la l´ınea 7 por el m´ etodoA(), el cual fue llamado por el m´etodo main() en la l´ınea 3 del archivo. Usando la lista de mensajes de error, se podr´ıa rastrear el lugar donde el error fue generado. Por supuesto, en una aplicaci´on m´as granda que contenga miles de l´ıneas de c´ odigo, la lista del historial de seguimiento de la pila podr´ıa ser m´as u ´til. En el m´ etodoA() En el m´ etodoB() En el m´ etodoC() Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 3 at DemoTrazaPila.m´ etodoC(DemoTrazaPila.java:16) at DemoTrazaPila.m´ etodoB(DemoTrazaPila.java:11) at DemoTrazaPila.m´ etodoA(DemoTrazaPila.java:7) at DemoTrazaPila.main(DemoTrazaPila.java:3) Cuando un programa usa varias clases, la desventaja es que el programador encuentra dif´ıcil encontrar la fuente original de una excepci´on. El m´etodo getMessage() de Throwable se usa para obtener informaci´on de un objeto Exception. Otro m´etodo u ´til de Exception es el m´etodo printStackTrace(). Cuando se atrapa un objeto Exception, se puede llamar printStackTrace() para mostrar una lista de m´etodos en la pila de llamadas y determinar el lugar de la sentencia que caus´o la excepci´on. La aplicaci´on DemoTrazaPila2, c´ odigo 13, en la cual el m´etodo printStackTrace() produce un seguimiento del camino tomado por el lanzamiento de una excepci´on. La llamada al m´ etodoB() 22
ha sido puesta en un bloque try para que la excepci´on pueda ser capturada. En vez de lanzar la excepci´on al sistema operativo, la aplicaci´on atrapa la excepci´on, muestra la lista del historial de seguimiento de la pila, y contin´ ua la ejecuci´on. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
public c l a s s DemoTrazaPila2 { public s t a t i c void main ( S t r i n g [ ] a r g s ) { m´etodoA ( ) ; } public s t a t i c void m´etodoA ( ) { System . out . p r i n t l n ( ”En e l m´etodoA ( ) ” ) ; try { m´etodoB ( ) ; } catch ( ArrayIndexOutOfBoundsException e r r o r ) { System . out . p r i n t l n ( ”En m´etodoA ( ) − S e g u i m i e n t o de l a p i l a : ” ) ; error . printStackTrace ( ) ; } System . out . p r i n t l n ( ”m´etodoA ( ) t e r m i n a normalmente . ” ) ; System . out . p r i n t l n ( ”La a p l i c a c i ´on podr´ıa c o n t i n u a r d e s d e e s t e punto . ” ) ; } public s t a t i c void m´etodoB ( ) { System . out . p r i n t l n ( ”En e l m´e todoB ( ) ” ) ; m´etodoC ( ) ; } public s t a t i c void m´etodoC ( ) { System . out . p r i n t l n ( ”En e l m´etodoC ( ) ” ) ; int [ ] a r r e g l o = { 0 , 1 , 2 } ; System . out . p r i n t l n ( a r r e g l o [ 3 ] ) ; m´etodoB ( ) ; } }
C´ odigo 13: Clase DemoTrazaPila2. Actividad 6. Ejecutar la aplicaci´ on DemoTrazaPila2 para revisar que la aplicaci´on no termina abruptamente. Generalmente no se quiere poner una llamada al m´etodo printStackTrace() en un programa terminado. El usuario t´ıpico de la aplicaci´on no tiene inter´es en los mensajes cr´ıtpico que son mostrados. Sin embargo, mientras se est´a desarrollando una aplicaci´on, printStackTrace() puede ser una herramienta u ´til para diagnosticar los problemas de la clase.
8.
Creaci´ on de clases Exception propias
Java proporciona m´ as de 40 categor´ıas de Exception que se pueden usar en los programas. Pero hay situaciones que no fueron consideradas en Java, por ejemplo, se podr´ıa querer declarar una Exception cuando el saldo bancario es negativo o cuando un participante externo intenta acceder su cuenta de correo electr´ onico. Diversas organizaciones tienen reglas espec´ıficas para datos excepcionales; por ejemplo, un n´ umero de empleado no deber´a exceder tres d´ıgitos, o un salario por hora no deber´ a ser menor que el salario m´ınimo legal. Por supuesto, se pueden manejar estas
23
situaciones potenciales de error con sentencias if, pero Java tambi´en permite crear sus propias clases Exception. Para crear su propia clase Exception lanzable, se debe extender una subclase de Throwable. Throwable tiene dos subclases, Exception y Error, las cuales son usadas para distinguir entre errores recuperables e irrecuperables. Como siempre se quiera que las excepciones propias se puedan recuperar entonces las clases se deber´ıan extender de la clase Exception. Se puede extender cualquier subclase Exception existente, como ArithmeticException o NullPointerException, pero generalmente se quiere heredar directamente de Exception. Cuando se crea una subclase Exception, es una convenci´ on terminar el nombre con Exception. La clase Exception contiene cuatro constructores que son: Exception(). Construye un nuevo objeto Exception con null como su mensaje detallado. Exception(String mensaje). Construye un nuevo objeto Exception con el mensaje detallado especificado. Exception(String mensaje, Throwable causa). Construye un nuevo objeto Exception con el mensaje detallado especificado y causa. Exception(Throwable causa). Construye un nuevo objeto Exception con la causa especificada y un mensaje detallado de causa.toString(), la cual t´ıpicamente contiene la clase y mensaje detallado de causa, o null si el argumento de la causa es null. La clase SaldoAltoException, c´ odigo 14, tiene un constructor que contiene una sola sentencia que pasa una descripci´ on de un error al constructor padre Exception. Este String podr´ıa ser recuperada si se llama al m´etodo getMessage() con un objeto SaldoAltoException. 1 2 3 4 5
public c l a s s S a l d o A l t o E x c e p t i o n extends E x c e p t i o n { public S a l d o A l t o E x c e p t i o n ( ) { super ( ” El s a l d o d e l c l i e n t e e s t ´a a l t o ” ) ; } }
C´ odigo 14: Clase SaldoAltoException. En la clase CuentaCliente, c´ odigo 15, se usa SaldoAltoException. La cabecera del constructor CuentaCliente indica que podr´ıa lanzar una instancia SaldoAltoException sin nombre, en el caso de que el saldo usado como argumento para el constructor exceda un l´ımite.
24
1 2 3 4 5 6 7 8 9 10 11
public c l a s s C u e n t a C l i e n t e { private int numCuenta ; private double s a l d o ; public s t a t i c double LIM CRED SUP = 2 0 0 0 0 . 0 0 ; public C u e n t a C l i e n t e ( int num , double s a l ) throws S a l d o A l t o E x c e p t i o n { numCuenta = num ; saldo = sal ; i f ( s a l d o > LIM CRED SUP ) throw new S a l d o A l t o E x c e p t i o n ( ) ; } }
C´ odigo 15: Clase CuentaCliente.
Nota. En la clase CuentaCliente se podr´ıa escoger instanciar un SaldoAltoException con nombre y lanzarlo cuando el balance exceda el l´ımite de cr´edito. Se mejora el rendimiento del programa esperando e instanciando un objeto an´onimo s´olo cuando sea necesario.
En la aplicaci´ on UsaCuentaCliente, 16, se le pide al usuario un n´ umero de cuenta y un saldo. Despu´es de que los valores son dados, se intenta construir un objeto CuentaCliente en un bloque try, como se ve en la l´ınea 12. Si el intento es exitoso, es decir, el constructor CuentaCliente no lanza una Exception, entonces la informaci´on del objeto es mostrada en cuadro de di´alogo. Pero si el constructor lanza una SaldoAltoException, el bloque catch, l´ıneas 16—20, lo recibe y muestra un mensaje. Una aplicaci´ on diferente podr´ıa mostrar el valor regresado por el m´etodo getMessage(), construir un objeto CuentaCliente con un saldo menor, o construir un tipo diferente de objeto el permita una saldo mayor. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
import j a v a x . swing . ∗ ; public c l a s s UsaCuentaCliente { public s t a t i c void main ( S t r i n g [ ] a r g s ) { int num ; double s a l d o ; String entrada ; e n t r a d a = JOptionPane . sh ow In put Di al og ( null , ” I n g r e s a r n´ u mero de c u e n t a ” ) ; num = I n t e g e r . p a r s e I n t ( e n t r a d a ) ; e n t r a d a = JOptionPane . sh ow In put Di al og ( null , ” I n g r e s a r e l s a l d o que s e debe ” ) ; s a l d o = Double . p a r s e D o u b l e ( e n t r a d a ) ; try { C u e n t a C l i e n t e c c = new C u e n t a C l i e n t e (num , s a l d o ) ; JOptionPane . showMessageDialog ( null , ” C l i e n t e # ” + num + ” t i e n e un s a l d o de $ ” + s a l d o ) ; } catch ( S a l d o A l t o E x c e p t i o n e s a ) { JOptionPane . showMessageDialog ( null , ” C l i e n t e # ” + num + ” t i e n e un s a l d o de $ ” + s a l d o + ” e l c u a l e s mayor que e l l´ımite d e l c r´e d i t o ” ) ; } } }
C´ odigo 16: Aplicaci´on UsaCuentaCliente.
25
En vez de codificar los mensajes de error en las clases de excepci´on, como se muestra en el c´ odigo 16, se podr´ıa considerar crear un cat´ alogo de mensajes posibles para usar. Esta forma da varias ventajas: Todos los mensajes son guardados en una localidad en vez de estar dispersos por todo el programa, haciendo m´ as f´ acil verlos y modificarlos. La lista de errores posibles sirve como una fuente de documentaci´on, listando problemas potenciales cuando se ejecute la aplicaci´on. Otras aplicaciones podr´ıan querer usar el mismo cat´alogo de mensajes. Si la aplicaci´ on ser´ a usada mundialmente, se puede proporcionar mensajes en m´ ultiples lenguajes, y otros programadores pueden usar la versi´on que es apropiada para su pa´ıs. Nota. Se puede lanzar cualquier tipo de excepci´on en cualquier momento, no s´olo las excepciones de su propia creaci´ on. Por ejemplo, dentro de cualquier programa se puede codificar throw(new RuntimeException());. Se podr´ıa querer hacerlo s´olo con una buena raz´on porque Java maneja RuntimeException por usted parando el programa. Como no se puede anticipar cada error posible, la respuesta autom´atica de Java es frecuentemente el mejor curso de la acci´on.
No se deber´ıa crear una cantidad excesiva de tipos de excepci´on especiales para sus clases, porque el ambiente de desarrollo de Java ya contiene un clase Exception que atrapar´a el error. Los tipos Exception adicionales agregan complejidad para otros programadores que usar´an sus clases. Sin embargo, cuando clases Exception apropiadas y especializadas proporcionan una forma elegante para manejar las situcaciones de error, permiten separar el c´odigo de manejo de error de la secuencia de eventos no excepcionales y usuales. Permiten a los clientes de sus clases manejar situaciones excepcionales de la forma m´ as adecuada para su aplicaci´on.
9.
Uso de afirmaciones
Algunos errores de l´ ogica no causan que un programa termine pero producen resultados incorrectos. Por ejemplo, si un programa de pago debe determinar el pago multiplicando horas trabajados por el pago por hora, pero si inadvertidamente se dividieron los n´ umeros, no ocurre un error en tiempo de ejecuci´on y no se lanza excepci´ on, pero la salida es incorrecta. Una afirmaci´ on es una capacidad del lenguaje Java que puede ayudar a detectar tales errores de l´ogica para depurar el programa. Se usa la sentencia assert para crear una afirmaci´on; cuando se usa una sentencia assert, se establece una condici´ on que deber´ıa ser true, y Java lanza una assertionError cuando no. La sintaxis de la sentencia assert es: assert expresi´ onBooleana : mensajeErrorOpcional La expresi´on booleana en la sentencia assert deber´a ser siempre true si el programa est´a trabajando correctamente. El mensajeErrorOpcional es mostrado si la expresi´ onBooleana es false. 26
La aplicaci´on ParImpar, c´ odigo 17, pide a un usuario un n´ umero y lo pasa a un m´etodo que determina si un valor es par. Dentro del m´etodo esPar(), el residuo es tomado cuando el par´ametro pasado es dividido por 2. Si el residuo despu´es de dividir por 2 es 1, resultado es puesto a false. Por ejemplo, 1, 3, y 5 todos son impares, ya que todos los resultados dan un valor de 1 cuando % 2 es aplicado a estos. Si el residuo despu´es de dividir por 2 no es 1, resultado es puesto a true. Por ejemplo, 2, 4, y 6 todos son pares, todos tienen un 0 de residuo cuando % 2 es aplicado a ellos. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
import j a v a . u t i l . Scanner ; public c l a s s ParImpar { public s t a t i c void main ( S t r i n g [ ] a r g s ) { Scanner e n t r a d a = new Scanner ( System . i n ) ; int numero ; System . out . p r i n t ( ” I n g r e s a r un n´ u mero >> ” ) ; numero = e n t r a d a . n e x t I n t ( ) ; i f ( esPar ( numero ) ) System . out . p r i n t l n ( numero + ” e s par ” ) ; else System . out . p r i n t l n ( numero + ” e s impar ” ) ; } public s t a t i c boolean esPar ( int numero ) { boolean r e s u l t a d o ; i f ( numero % 2 == 1 ) resultado = false ; else r e s u l t a d o = true ; return r e s u l t a d o ; } }
C´odigo 17: ParImpar. La siguiente salida se muestra correcta excepto en las dos u ´ltimas ejecuciones. Los valores -5 y -7 son clasificados como pares sin embargo son impares. Una afirmaci´on podr´ıa ayudar a depurar la aplicaci´on. $ java ParImpar Ingresar un n´ umero 4 es par $ java ParImpar Ingresar un n´ umero 5 es impar $ java ParImpar Ingresar un n´ umero -4 es par $ java ParImpar Ingresar un n´ umero -5 es par $ java ParImpar Ingresar un n´ umero -7 es par
>> 4
>> 5
>> -4
>> -5
>> -7
27
En el c´odigo 18 se tiene una nueva versi´on del m´etodo es esPar() al cual se le ha agregado la sentencia assert en la l´ınea 7 (adem´ as las llaves). La sentencia afirma que cuando el residuo de un n´ umero dividido por 2 no es 1, deber´a ser 0. Si la expresi´on no es true, un mensaje es creado usando los valores de numero y su residuo despu´es de dividir por 2. 1 2 3 4 5 6 7 8 9 10
public s t a t i c boolean esPar ( int numero ) { boolean r e s u l t a d o ; i f ( numero % 2 == 1 ) resultado = false ; else { r e s u l t a d o = true ; a s s e r t numero % 2 == 0 : numero + ” % 2 e s ” + numero % 2 ; } return r e s u l t a d o ; }
C´ odigo 18: El m´etodo defectuoso esPar con una afirmacion. Si se agrega la afirmaci´ on entonces al momento de ejecutar el programa se debe usar la opci´on -ea enable assertion para que sea lanzada la excepci´on cuando la afirmaci´on no sea verdadera. Al ejecutar el programa ParImpar y con el valor -5, el programa muestra el mensaje de que una AssertionError fue lanzada y que el valor de -5 % 2 es -1, no 1 como se hab´ıa asumido. El operador residuo da un valor negativo cuando el primer operando es negativo. Los ajustes que podr´ıan realizarse al c´odigo para que muestre la informaci´on correctamente son alguno de los siguientes: 1. Calcular el valor absoluto del par´ ametro numero del m´etodo esPar() antes de usar el operador residuo: numero = Math.abs(numero); 2. Cambiar la sentencia de selecci´ on para probar por valores pares primero revisando si numero % 0 es cero. if (numero % 2 == 1) resultado = true; else resultado = false; 3. Otras opciones son: mostrar un mensaje de error cuando valores negativos sean encontrados, invertir los valores del resultado cuando el par´ametro sea negativo, o lanzar una excepci´ on. Un programador experimentado podr´ıa encontrar el error sin usar una afirmaci´on. Quiz´as por que sab´ıa que al aplicarlo con un valor negativo el resultado es negativo. O metiendo sentencias para mostrar valores en puntos estr´ ategicos en el programa, pero despu´es de que el error es encontrado, las sentencias extras puestas deben ser quitadas. En cambio, cualquier sentencia assert puede ser dejada, y si no se usa la opci´ on -ea al ejecutar el programa, el usuario no ver´a evidencia del uso
28
de sentencias assert. Colocando sentencias assert en lugares clave del programa puede reducir el tiempo de desarrollo y depuraci´ on. No se quieren usar afirmaciones para revisar cada tipo de error que podr´ıa ocurrir en un programa. Si se quiere asegurar que un usuario ingrese datos num´ericos, se deber´ıa usar las t´ecnicas de manejo de excepciones que dan los medios para que el programa se recupere de ese error. Si se quiere asegurar que los datos caen dentro de un cierto rango, se deber´ıa usar una decisi´on o un ciclo. Las afirmaciones son de ayuda en la etapa de desarrollo de un programa, no cuando est´a en producci´ on y en las manos de los usuarios.
29