TEMA 8 Excepciones en Java Manejo de Excepciones Cuando un programa Java viola las restricciones semánticas del lenguaje (se produce un error), la máquina virtual Java comunica este hecho al programa mediante una excepción. La aplicación Java no debe morir y generar un core (o un crash en caso del DOS), al contrario, se lanza (throw) una excepción y se captura (catch) para resolver esa situación de error. Muchas clases de errores pueden provocar una excepción, desde un desbordamiento de memoria o un disco duro estropeado hasta un disquete protegido contra escritura, un intento de dividir por cero o intentar acceder a un vector fuera de sus límites. Cuando esto ocurre, la máquina virtual Java crea un objeto de la clase exception o error y se notifica el hecho al sistema de ejecución. Se dice que se ha lanzado una excepción ( Throwing Exception ). Un método se dice que es capaz de tratar una excepción ( Catch Exception ) si ha previsto el error que se ha producido y prevé también las operaciones a realizar para recuperar el programa de ese estado de error. En el momento en que es lanzada una excepción, la máquina virtual Java recorre la pila de llamadas de métodos en busca de alguno que sea capaz de tratar la clase de excepción lanzada. Para ello, comienza examinando el método donde ese ha producido la excepción; si este método no es capaz de tratarla, examina el método desde el que se realizó la llamada al método donde se produjo la excepción y así sucesivamente hasta llegar al último de ellos. En caso de que ninguno de los métodos de la pila sea capaz de tratar la excepción, la máquina virtual Java muestra un mensaje de error y el programa termina. Los programas escritos en Java también pueden lanzar excepciones explícitamente mediante la instrucción throw, lo que facilita la devolución de un código de error al método que invocó el método que causó el error. Veamos un ejemplo. Jesús Cáceres Tello Pág. 1-11
public class Excepciones1 { public static void main(string[] arg) { metodo(); static void metodo() { int divisor = 0; int resultado = 100/divisor; System.out.println( Resultado: + resultado); System.out.println( Una Suma: + (3+4)); Ejemplo 8.1: Lanzando una excepción Si intentamos ejecutar este programa obtendremos el siguiente resultado: Lo que ha ocurrido es que la máquina virtual Java ha detectado una condición de error y ha creado un objeto de la clase java.lang.arithmeticexception. Como el método donde se ha producido la excepción no es capaz de tratarla, se trata por la máquina virtual Java, que muestra el mensaje de error anterior y finaliza la ejecución del programa. Generación de Excepciones en Java Partiendo de la base de que cualquier método puede lanzar excepciones en Java, es aconsejable tener declaradas todas las posibles excepciones que se puedan generar en dicho método, para lo cual se utilizará la cláusula throws de la declaración de métodos. Para que se pueda lanzar una excepción es necesario crear un objeto de tipo Exception o alguna de sus subclases como ArithmeticException y lanzarlo mediante la instrucción throw como se muestra en el siguiente ejemplo: Jesús Cáceres Tello Pág. 2-11
class LanzaExcepcion { public static void main(string argumentos[]) throws ArithmeticException { int i=1, j=2; if (i/j< 1) throw new ArithmeticException(); else System.out.println(i/j); Ejemplo 8.2: Lanzamiento de una excepción desde el método main de una clase Jerarquía de clases de las excepciones en Java Jesús Cáceres Tello Pág. 3-11
Un gráfico un poco más claro de esta jerarquía de clases Captura de Excepciones Un manejador de excepciones es una porción de código que se va a encargar de tratar las posibles excepciones que se puedan generar. En Java, de forma similar a C++ se pueden tratar las excepciones previstas por el programador utilizando unos mecanismos, los manejadores de excepciones, que se estructuran en tres bloques: El bloque try El bloque catch El bloque finally (no existente en C++) El bloque try Lo primero que hay que hacer para que un método sea capaz de tratar una excepción generada por la máquina virtual Java es encerrar las instrucciones susceptibles de generarla en un bloque try. Jesús Cáceres Tello Pág. 4-11
Bloque de Instrucciones Cualquier excepción que se produzca dentro del bloque try será analizado por el bloque o bloques catch que se verá en el punto siguiente. En el momento en que se produzca la excepción, se abandona el bloque try y, por lo tanto, las instrucciones que sigan al punto donde se produjo la excepción no serán ejecutadas. Cada bloque try debe tener asociado al menos un bloque catch El bloque catch Por cada bloque try pueden declararse uno o varios bloques catch, cada uno de ellos capaz de tratar un tipo u otro de excepción. Bloque de Instrucciones catch (TipoExcepción nombrevariable) { Bloque de Instrucciones del primer catch catch (TipoExcepción nombrevariable) { Bloque de Instrucciones del segundo catch Para declarar el tipo de excepción que es capaz de tratar un bloque catch, se declara un objeto cuya clase es la clase de la excepción que se desea tratar o una de sus superclases. Veámoslo con un ejemplo: class ExcepcionTratada { public static void main(string[] arg) { int i=5; j=0; int k=i/j; System.out.println( Esto no se va a ejecutar ); catch (ArithmeticException ex) { System.out.println( Has intentado dividir por cero ); System.out.println( Fin del programa ); Ejemplo 8.3: Clase que captura la excepción producida por una división por 0 Jesús Cáceres Tello Pág. 5-11
Cuando se intenta dividir por cero, la máquina virtual Java genera un objeto de la clase ArithmeticException. Al producirse la excepción dentro de un bloque try, la ejecución del programa se pasa al primer bloque catch. Si la clase de la excepción se corresponde con la clase o alguna subclase de la clase declarada en el bloque catch, se ejecuta el bloque de instrucciones catch y a continuación se pasa el control del programa a la primera instrucción a partir de los bloques try-catch. Como puede verse, también se podría haber utilizado en la declaración del bloque catch, una superclase de la ArithmeticException. Por ejemplo: catch (RuntimeException ex) { o catch (Exception ex) { Sin embargo, es mejor utilizar excepciones lo más cercanas al tipo de error previsto, ya que lo que se pretende es recuperar al programa de alguna condición de error y si se meten todas las condiciones en el mismo saco, seguramente habrá que averiguar después qué condición de error se produjo para poder dar una respuesta adecuada. El bloque finally El bloque finally se utiliza para ejecutar un bloque de instrucciones sea cual sea la excepción que se produzca. Este bloque se ejecutará en cualquier caso, incluso si no se produce ninguna excepción. Sirve para no tener que repetir código en el bloque try y en los bloques catch Bloque de Instrucciones del try catch (TipoExcepción nombrevariable) { Bloque de Instrucciones del primer catch catch (TipoExcepción nombrevariable) { Bloque de Instrucciones del segundo catch.. finally { Bloque de Instrucciones de finally Jesús Cáceres Tello Pág. 6-11
Creación de Excepciones Se pueden definir excepciones propias en Java, no hay por qué limitarse a las predefinidas, bastará con que la clase extienda de la clase Exception y proporcionar la funcionalidad extra que requiera el tratamiento de esa excepción. Por ejemplo, consideremos un programa cliente/servidor. El código cliente se intenta conectar al servidor, y durante 5 segundos se espera a que conteste el servidor. Si el servidor no responde, el servidor lanzaría la excepción de time-out: class ServerTimeOutException extends Exception { public void conectame( String nombreservidor ) throws Exception { int exito; int puerto = 80; exito = open( nombreservidor,puerto ); if( exito == -1 ) throw ServerTimeOutException; Ejemplo 8.4: Método que lanza una excepción no definida (Exception) Si se quieren capturar las propias excepciones, se deberá utilizar la sentencia try: public void encuentraservidor() { conectame( servidordefecto ); catch( ServerTimeOutException e ) { System.out.println( "Time-out del Servidor, intentando alternativa", 5,5 ); conectame( servidoralterno ); Cualquier método que lance una excepción también debe capturarla, o declararla como parte de la interface del método. Cabe preguntarse entonces, el porqué de lanzar una excepción si hay que capturarla en el mismo método. La respuesta es que las excepciones no simplifican el trabajo del control de errores. Tienen la ventaja de que se puede tener muy localizado el control de errores y no tenemos que controlar millones de valores de retorno, pero no van más allá. Jesús Cáceres Tello Pág. 7-11
Todos los métodos Java utilizan la sentencia throw para lanzar una excepción. Esta sentencia requiere un sólo argumento (un objeto Throwable) Veamos el siguiente código de la función pop() cuyo propósito es sacar el elemento superior de la pila. public Object pop() throws EmptyStackException { Object obj; if (size == 0) throw new EmptyStackException(); obj = objectat(size - 1); setobjectat(size - 1, null); size--; return obj; Ejemplo 8.5: Otro método lanzando una excepción de tipo Pila vacía El método pop() comprueba si la pila no está vacía. Si lo está, crea un nuevo objeto de la clase EmptyStackException y lo lanza, aunque en el método no se genere alguna excepción debido a lo bien validado que se encuentra, nosotros somos quienes lo lanzamos. Además por lógica, la clase EmpyStackException es una subclase de Thowable, ya que en cualquier otro caso, no se podría lanzar dicha excepción. Algo que se debe considerar aquí, es que en la declaración del procedimiento añade el siguiente código throws EmptyStackException, throws es una palabra reservada de java, y EmpyStackException es una subclase de Throwable. El uso de throws permite evitarnos la molestia de capturar las excepciones del tipo de excepciones indicadas después de esta palabra (las clases van separadas por coma), esto debido a que deja al sistema de ejecución Java que decida cuál sería la mejor opción en caso de que ocurriera una excepción de los tipos indicados. Para crear una clase de tipo Throwable tan solo hay que hacerla heredar de una clase del tipo Exception o subclases de ella y definir el constructor con un mensaje de entrada: public class DividePorCeroException extend ArithmeticException { public DividePorCeroException (String mensaje) { super(mensaje); De esta forma cualquier clase puede lanzar este tipo de excepción: public double divider(int num, int den) throws DividePorCeroException { if (den==0) throw new DividePorCero( Error, estás dividiendo por cero. ); return ((double) num/(double) den); Jesús Cáceres Tello Pág. 8-11
Laboratorio 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. // 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; 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 ) entrada.close(); // Siempre queda cerrado catch ( Exception e ) { System.out.println( "Excepcion: " + e.getmessage() ); System.out.println( "Fichero cerrado." ); Jesús Cáceres Tello Pág. 9-11
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 Jesús Cáceres Tello Pág. 10-11
Ejercicios Construir una aplicación que mediante el uso de excepciones diseñadas por el programa identifique el número de parámetros que se pasan a la aplicación en su llamada por teclado. Para ello se diseñarán tres tipos de excepciones, para uno, dos o más parámetros. Construir una jerarquía de clases de excepciones que identifiquen si el/los parámetros pasados son números o letras. Si son números se procederá a su división y si el segundo parámetro fuese 0 se identificará este hecho mediante el lanzamiento de su excepción correspondiente. Jesús Cáceres Tello Pág. 11-11