Manejando Excepciones en Java

La mayoría de veteranos en el desarrollo de software dicen que el 80% del trabajo se realiza en el 20% del tiempo. El 80% se refiere al esfuerzo requerido para comprobar y encargarse de los errores. En la mayoría de los lenguajes, escribir un código de programa que cmopruebe y trate con errores es tedioso y hincha la fuente de la aplicación en un confuso spaghetti. Aún así, la detección de errores y el manejo de estos puede ser el ingrediente mas importante de cualquier aplicación robusta. Java arma a los desarrolladores con un mecanismo elegante para manejar errores que produce un código eficiente y organizado: el manejo de excepciones o exception handling. Esto permite a los desarrolladores detectar errores fácilmente sin escribir un código especial que retorne variables. Incluso mejor, mantiene el código que maneja la excepción limpio y separado del código que genera la excepción. Cambien nos permite usar el mismo mecanismo de tratamiento de excepciones para tratar con un rango amplio de posibles excepciones.


  • Capturar una Excepcion usando try y catch

    Antes de empezar, vamos a explicar un poco la terminología. El término “exception” (excepción en castellano) significa “condición excepcional” y es una ocurrencia que altera el flujo normal del programa. Un montón de cosas nos pueden llevar a excepciones, incluyendo fallos de hardware, agotamiento de recursos, y viejos bug antiguos. Cuando un evento excepcional ocurre en Java, se dice que la excepción es lanzada, o “thrown”. El código que es responsable de hacer algo sobre la excepción es llamado “manejador de excepción” o “exception handler”, y “coge” la excepción lanzada.

    El manejo de excepciones trabaja transfiriendo la ejecución del programa a un exception handler apropiado cuando la excepción ocurre. Por ejemplo, si llamamos a un método que abre un archivo pero el archivo no puede ser abierto, la ejecución del método se interrumpirá, y el código que hemos escrito que trate con esta situación será ejecutado. Por lo tanto, necesitamos una manera de decirle a la JVM que código ejecutar cuando una excepción concreta ocurre. Para hacer esto, usamos las palabras try y catch. El try es usado para definir el bloque de código donde la excepción puede ocurrir. Este bloque de código es llamado como “zona asegurada”, o en otras palabras, “El código arriesgado va aquí”. Una o mas clausulas catch pueden abarcar unas excepciones específicas (o grupo de excepciones) en un bloque de código que las manejan. Aquí vemos un ejemplo de como se vería en código:

    		try{
    			// Esta es la primera línea de la region asegurada
    			// Está governada por la palabra clave try
    			// Aquí ponemos el código que puede causar algun tipo de excepcion
    			// Podemos tener muchas lineas aquí o solo una
    		}
    		catch (MyFirstException){
    			// Aquí va el código que maneja la excepción
    			// Esta es la siguiente linea
    			// Esta es la tercera linea
    		}
    		catch/MySecondException){
    			// Aquí va el código que maneja otra excepción
    		}
    		
    		// Otro código que no está asegurado, o que no tenga riesgo de lanzar excepcion
    		

    En este ejemplo de pseudocódigo, el bloque try contiene el código que puede ocasionar una excepción. Luego nos encontramos con un bloque catch que se encarga de una excepción que hemos llamado MyFirstException, aquí colocaremos el código que se encargará de realizar las acciones que veamos oportunas cuando ese error ocurra. A continuación vemos otro bloque catch que almacena el código que maneja la excepción MySecondException. Tenemos que observar que los bloques catch siguen inmediatamente al bloque try. Esto es un requisito; si tenemos uno o mas bloques catch, deben inmediatamente seguir al bloque try. Adicionalmente, los bloques catch deben seguirse los unos a los otros, sin que exista ningún otro tipo de bloque entre ellos. También, el orden en el cual los bloques catch aparecen importe, pero lo veremos mas adelante.

    La ejecución del código asegurado empieza en la línea 2. Si el programa se ejecuta y pasa la línea 5 sin ninguna excepción siendo lanzada, la ejecución será transferida después de todos los bloques catch, y seguirá la ejecución del programa hacia abajo. Sin embargo, si en algún momento desde las lineas 2 hasta la 5 se lanza alguna excepción del tipo MyFirstExcepción, la ejecución se irá inmediatamente al bloque del catch donde se maneja este tipo de excepción, y entonces, la ejecución saltará de nuevo después del último catch y seguirá la ejecución.

    Hay que comprender, que si una excepción ocurre, por así decirlo en la línea 3 del bloque try, el resto de lineas en el bloque try no serán ejecutadas. Una vez que el control salta al bloque catch, nunca retorna para completar lo que queda del bloque try. Esto es exactamente lo que queremos, aunque imaginemos que nuestro código se parece al siguiente pseudocódigo:

    		try{
    			getTheFileFromOverNetwork
    			readFromTheFileAndPopulateTable
    		}
    		catch(CantGetFileFromNetwork){
    			displayNetworkErrorMessage
    		}
    		

    El pseudocódigo anterior demuestra como trabajaríamos típicamente con excepciones. El código que es dependiente de una operación que tiene riesgo es agrupado en un bloque try para que de algún modo, si la primera operación falla, no querremos continuar intentando ejecutar otro código que tiene todas las garantías de fallar. En el ejemplo de pseudocódigo, no queremos permitir que la lectura del archivo siga llevándose a cabo si puede ocurrir que no podemos tener el archivo de la red antes.

    Uno de los beneficios del uso del manejo de excepciones es que el código maneje cualquier excepción particular que puede ocurrir en la región gobernada necesita ser escrita una sola vez. Retornando a nuestro anterior ejemplo de código, puede haber 3 diferentes lugares en nuestro bloque try donde se puede generar un MyFirstException, pero donde sea que ocurra será tratada por el mismo bloque catch. Discutiremos mas sobre los beneficios del manejo de excepciones mas adelante

  • Usando finally

    Aunque try y catch proveen un mecanismo terrorífico para atrapar y manejar excepciones, nos queda un problema sobre como limpiar todo si una excepción ocurre. Debido a que la ejecución se transfiere fuera del bloque try tan pronto como una excepción es lanzada, no podemos poner nuestro código de “limpieza” al final del bloque try y esperar que sea ejecutado si una excepción ocurre. También sería una mala idea colocar este código de limpieza en cada uno de los bloques catch, vamos a ver el porqué.

    Los exception handlers son un lugar muy pobre para limpiar todo después del bloque try porque cata handler entonces requerirá una copia propia del código de limpieza. Si, por ejemplo, hemos asignado un socket de red o hemos abierto un archivo en algún lugar en la región asegurada, cada exception handler entonces tendría que cerrar el archivo o liberar el socket. Esto haría muy fácil olvidar de hacer al limpieza, y también nos hace tener una gran cantidad de código redundante. Para direccionar este problema, Java ofrece el bloque finally.

    Un bloque finally encierra el código que siempre será ejecutado en algún punto desués del bloque try, aunque una excepción sea lanzada o no. Incluso si hay una sentencia return en el bloque try, el bloque finally se ejecuta justo después de que el return sea encontrado, y después se ejecuta el return.

    Esta es la zona correcta donde podemos cerrar los archivos, liberar los sockets, y realizar cualquier otro tipo de limpieza que nuestro código requiera. Si el bloque try se ejecuta sin ninguna excepción, el bloque finally es ejecutado inmediatamente después de que el bloque try se complete. Si hubiera una excepción que es lanzada, el bloque finally se ejecutará inmediatamente después del bloque catch que se haay completado. Vamos a ver otro ejemplo de pseudocódigo:

    		try{
    			// Esta es la primera línea de la "región asegurada"
    		}
    		catch(MyFirstException){
    			// Aquí el código que maneja esta excepcion
    		}
    		finally {
    			// Aquí el código que libera cualquier recurso
    			// asignado en la clausula try
    		}
    		// Mas código aquí
    		

    Como antes, la ejeccuión empieza en la primera línea del bloque try, en al linea 2. Si no hay excepciones que sean lanzadas en el bloque try, la ejecución se transfiere a la linea 11, la primera linea del bloque finally. De otro modo, si es lanzada la excepción MySecondException mientras el código en el bloque try se está ejecutando, la ejecución se transfiere ala primera línea del catch que se encarga de esta excepción. Después de que el código en la clausula catch sea ejecutado, el programa se mueve a al linea 11, la primera línea de la clausula finally. Tenemos que tener claro que el bloque finally SIEMPRE SE EJECUTA. Bueno, tendremos que redefinir esto un poco, tenemos que tener presente que siempre se ejecutará. Si una excepción no es lanzada, se ejecuta el finally. Si una excepción es lanzada, el finally se ejecuta. Si la excepción no es recogida por ningún catch, finally se ejecuta. Mas tarde veremos algunos escenarios donde el finally podría no completarse o ejecutarse.

    Recordemos, la clausula finally no es requerida. Si no escribimos nada, el código compilará y se ejecutará bien. De hecho, si no tenemos recursos que limpiar después de que nuestro bloque try se ejecute, posiblemente no necesitemos un bloque finally. Cambien podemos encontrarnos que el compilador no necesite incluso una clausula catch, a veces nos encontraremos con códigos que tienen un bloque try e inmediatamente están seguidos por un bloque finally. Semejante código es útil cuando la excepción va a ser pasada o retornada al método que ha llamado a esta función, como explicaremos mas tarde. Usando un bloque finally se nos permite ejecutar un código de limpieza incluso cuando no hay clausula catch.

    El siguiente código legal demeustra un try con un finally pero sin ningún catch:

    		try{
    			// hacer cosas
    		}
    		finally{
    			// limpiar
    		}
    		

    El siguiente código es legal y nos muestra un try, catch y finally:

    		try{
    			// hacer cosas
    		} catch(SomeException ex){
    			// crear el manejo de la excepcion
    		} finally {
    			// limpiar
    		}
    		

    El siguiente código es ILEGAL y muestra un try sin catch o finally:

    		try{
    			// hacer cosas
    		}
    		// se necesita un catch o finally aqui
    		System.out.println("fuera del bloque try");
    		

    El siguiente código es ILEGAL y muestra un catch en un lugar equivocado:

    		try{
    			// hacer cosas
    		}
    		// no se puede tener código entre try/catch
    		System.out.println("Fuera del bloque try");
    		catch(Exception ex){ }
    		
  • Propagando Excepciones sin Capturar

    ¿Por qué no poner catch requeridos?. ¿Qué pasa cuando una excepción es lanzada en un bloque try cuando no hay una clausula catch que la espera?. Actualmente, no hay ningún requerimiento de que nuestro código tenga un bloque catch para cada excepción posible que pueda ser lanzada desde un bloque try correspondiente. De hecho, es dudoso que se pudiera lograr tal. Si un método no provee una clausula catch para una excepción particular, podemos decir que el método está pasandole el problema de la excepción.

    ¿Qué pasa con estas excepciones?. Antes de discutir esto, tenemos que resumir brevemente el concepto del call stack o pila de llamadas. la mayoría de los lenguajes tienen el concepto de la pila de métodos o la pila de llamadas. Simplemente explicado, la pila de llamadas es uan cadena de métodos que nuestro programa ejecuta para llegar al método actual. SI nuestro programa empieza en el método main() y main() llama al método a(), el cual llama al método b(), el cual llama al método c(), l apila de llamadas consistiría en lo siguiente:

    		c
    		b
    		a
    		main
    		

    Representaremos la pila como algo que crece hacia arriba. Como podemos ver, el último método en ser llamado está en la cima de la pila, mientras que el primer método que hemos llamado está debajo del todo. Este método que está en la cima de la pila sería el método que estamos ejecutando actualmente. Si nos movemos hacia abajo en la pila de llamadas, nos estamos moviendo desde el método actual hasta el método que lo ha llamado previamente.

    Ahora vamos a ver que pasa con estas excepciones las cuales no controla un método. Imaginemos un edificio, de 5 pisos de alto, y en cada planta hay un balcón. Ahora imaginemos que en cada balcón, hay una persona que tiene un guante de baseball. Las excepciones son como las bolas que caen de persona en persona, empezando por el tejado. Una excepción es lanzada primero desde arriba, es decir, desde la cima de la pila (la persona en el tejado), y si no es cogida por la misma persona que la ha tirado, caen hacia abajo en la pila de llamadas al método anterior (o la persona que se encuentra en el piso de abajo), y así hasta que sea recogida o hasta que caiga en la parte mas baja de la pila de llamadas. Esto es llamado Propagación de Excepciones.

    Si una excepción alcanza el fondo de la pila de llamadas, es como si hubiera tenido una caída muy larga, la bola explota, y esto es lo que hará nuestro programa. Una excepción que no es nunca recogida causará que nuestra aplicación pare de ejecutarse. Una descripción de la excepción será mostrada, y se imprimirá la pila de llamadas. Esto nos ayudará a depurar nuestra aplicación diciendo que excepción ha sido lanzada, desde que método fue lanzada, y como estaba la pila hasta ahora.

  • Definiendo Excepciones

    Hemos estado discutiendo las excepciones como un concepto. Sabemos que son lanzadas cuando un problema de algún tipo ocurre, y sabemos que efecto tiene en el flujo del programa. En esta sección vamos a desarrollar el concepto adicional y usaremos las excepciones en código funcional de Java. Antes hemos dicho que una excepción es una ocurrencia que altera el flujo normal del programa. Pero como esto es Java, cualquier cosa que no sea primitive….es un objeto. Las excepciones no son, por así decirlo, excepciones a esta regla. Cada excepción es una instancia de una clase que tiene la clase Exception en su jerarquía de herencia. En otras palabras, las excepciones son siempre subclases de java.lang.Exception. Cuando una excepción es lanzada, un objeto particular y subtipo de Exception es instandiado y llevado hasta el exception handler como argumento a la clausula catch. Podemos ver un ejemplo en el siguiente código:

    		try{
    			// algún código aqui
    		}
    		catch(ArrayIndexOutOfBoundsException e){
    			e.printStackTrace();
    		}
    		

    En este ejemplo, e es una instancia de la clase ArrayIndexOutOfBoundException, y como cualquier otro objeto, podemos llamar a sus métodos.

  • Jerarquía de Excepciones

    Todas las clases de excepciones son subtipos de la clase Exception. Esta clase deriva de la clase Throwable (la cuál deriva de la clase Object). De la clase Throwable, derivan 2 subclases: Exception y Error. Las clases que derivan de Error representan situaciones inusuales que no son causadas por errores del programa, e indican cosas que normalmente no deberían pasar durante la ejecución del programa, como la JVM quedándose sin memoria. Generalmente, nuestra aplicación no se podrá recuperar de un Error, por lo que no es un requisito intentar controlarlas. Si nuestro código no las controla (y normalmente no lo hará), seguirá compilando sin problema. Aunque a menudo son considerado como condiciones excepcionales, los Errores no son técnicamente excepciones porque no derivan de la clase Exception.

    En general, una excepción representa algo que ocurre, no como un error en la programación, sino como algún recurso que no está disponible o que otra condición es requerida para una correcta ejecución no está presente. Por ejemplo, si nuestra aplicación tiene que comunicarse con otra aplicación o ordenador que no está respondiendo, esto es una excepción que no está causada por un bug. Estas excepciones son casos especiales porque a veces hacen indicar que son errores del programa. Pueden también representar raros, y condiciones difíciles de controlar. Las excepciones en tiempo de ejecución las discutiremos mas adelante.

    Java provee de muchas clases de excepciones, de las cuales la mayoría tienen un nombre bastante descriptivo. Hay 2 manera de obtener información sobre una excepción. La primera es del tipo de excepción por sí misma. La siguiente es de la información que podemos obtener del objeto de la excepción. La clase Throwable (en la parte superior del árbol de herencia de las excepciones) provee a sus descendientes con algunos métodos que son útiles para los controladores de excepciones o exception handlers. Uno de estos métodos es printStackTrace(). Como esperábamos, si queremos llamar al método printStackTrace() de un objeto, como en anteriores ejemplos, un trazo de la pila nos dirá donde ocurrió la excepción.

    Anteriormente discutimos que que la pila de llamadas o call stack se construye hacia arriba con el método mas reciente llamado en la cima. Veremos que el método printStackTrace() imprime el primer método mas reciente primero y luego continua hacia abajo, imprimiento información sobre cada método y como funciona.

  • Manejando Jerarquía de Clases entera de Excepciones

    Hemos discutido que la palabra clave catch nos permite especificar un tipo particular de excepcion a controlar. Podemos coger mas de un tipo de excepción en una clausula catch singular. Si la clase de excepción que especificamos en la clausula catch no tiene subclases, entonces solo la clase especifica de excepción será controlada. Sin embargo, si al clase especificada en la clausula catch tiene subclases, cualquier objeto de excepción que las subclases de la clase especifica serán capturadas también.

    Por ejemplo, la clase IndexOutOfBoundsException tiene 2 subclases, ArrayIndexOutOfBoundsException y StringIndexOutOfBoundsException. Puede que queramos escribir un exception handler que trabaje con la excepción producida por cualquier tipo de error referente a los límites, pero puede que no seamos conscientes de que excepción tenemos actualmente. En este caso, podemos escribir una clausula catch como la siguiente.

    		try{
    			// Código que puede lanzar una excepción de limites
    		}
    		catch (IndexOutOfBoundsException e){
    			e.printStackTrace();
    		}
    		

    Si algún código en el bloque try lanza la excepción ArrayIndexOutOfBoundsException o StringIndexOutOfBoundsException, la excepción será controlada y manejada. Esto puede ser conveniente, pero debería ser usada escasamente. Especificando superclase de la excepción de la clase de excepción en nuestra clausula catch, estamos descartando información de valor sobre la excepción. Podemos, por supuesto, encontrar que clase de excepción exactamente estamos teniendo, pero si vamos a hacer esto, será mejor que escribamos clausulas catch separadas que recojan cada tipo de excepción que nos interese.

  • Emparejamiento de Excepciones

    Si tenemos una jerarquía de excepciones compuesta de una superclase de excepción y un número de subtipos, y estamos interesados en controlar uno de los subtipos de una manera especial pero queremos controlar el resto también, tendríamos que crear solo 2 clausulas catch.

    Cuando una excepción es lanzada, Java intentará encontrar (mirando en las clausulas catch desde arriba hacia abajo) una clausula catch del tipo de la excepción. Si no encuentra ninguna, buscará un controlador para el supertipo de la excepción. Si no encuentra una clausula catch que pueda emparejarse con el supertipo de la excepción, entonces la excepción es propagada hacia abajo en la pila de llamadas. Este proceso es llamado emparejamiento de excepciones o exception matching. Vamos a ver un ejemplo:

    		import java.io.*;
    		public class ReadData{
    			public static void main (String args[]){
    				try{
    					RandomAccessFile raf = new RandomAccessFile("myfile.txt", "r");
    					byte b[] = new byte[1000];
    					raf.readFully(b, 0, 1000);
    				}
    				catch(FileNotFoundException e){
    					System.err.println("File not found");
    					System.err.println(e.getMessage());
    					e.printStackTrace();
    				}
    				catch(IOException e){
    					System.err.println("IO Error");
    					System.err.println(e.toString());
    					e.printStackTrace();
    				}
    			}
    		}
    		

    Este pequeño programa intenta abrir un archivo y leer algún dato de el. Abrir y leer archivos pueden generar algunas excepciones, de las cuales la mayoría son del tipo IOException. Imaginemos que en este programa estamos interesados en saber solo cuando ocurre la excepción FileNotFoundException. De otro modo, no nos importa exactamente el problema que sea.

    FileNotFoundException es una subclase de IOException. Por lo tanto, podremos controlarlo en la clausula catch que recoge todos los subtipos de IOException, pero entonces tendríamos que estudiar que excepción es para determinar si ha sido FileNotFoundException. En vez de esto, hemos creado un exception handler especial para la excepción FileNotFoundException y otro handler exception para los demás subtipos de IOException.

    Si este código genera la excepción FileNotFoundException, será controlado por la primera clausula catch. Si genera otro tipo de IOException, será controlado por el otro bloque catch que comienza inmediatamente después. Si alguna otra excepción es generada, como una runtime exception de algún tipo, ninguna clausula catch de las que tenemos será ejecutada y será propagada hacia abajo en la pilla de llamadas.

    Vemos que la clausula catch para FileNotFoundException fue colocada encima del handler para la IOException. Esto es muy importante, lo hubiéramos hecho de la manera opuesta, el programa no compilaría. Los manejadores de excepciones para una excepción específica siempre debe ser colocado encima de los que se encargan de excepciones mas generales. El siguiente ejemplo no compilaría:

    		try{
    			// operaciones IO de riesgo
    		} catch (IOException e){
    			// manejamos IOEXceptions generales
    		} catch(FileNotFoundException ex){
    			// manejamos solo FileNotFoundException
    		}
    		

    Vamos a volver al ejemplo de las personas con el guante de baseball y vamos a imaginar que la mayoría de guantes generales son mas grandes, y pueden capturar diferentes tipos de bolas. Un guante de IOException es mas grande y flexible y puede coger cualquier tipo de IOException. Por lo que si la persona en la 5 planta tiene un guante IOEXception, no puede ayudar pero puede coger la bola de FileNotFoundException. Y si la persona en la segunda planta tiene un guante para FileNotFoundException, esa bola FileNotFoundException nunca llegará a el, ya que ha sido parada por el tipo que se encuentra en el quinto piso.

  • Declaración de excepciones y Interface pública

    ¿Como sabemos que algún método lanza una excepción que tenemos que controlar?. Así como un método debe especificar que tipo y cuantos argumentos acepta y cual es retornado, una excepción que el método puede lanzar debe ser declarada. La lista de excepciones que pueden ser lanzadas, es una parte de la interface pública del método. La palabra clave throws es usada como se muestra en el siguiente ejemplo:

    		void myFunction() throws MyException1, MyException2{
    			// El código del método va aqui
    		}
    		

    Este método tiene como tipo de retorno un void, no acepta argumentos, y declara que puede lanzar uno o dos tipos de excepciones: ya sea del tipo MyException1 o MyException2.

    Supongamos que el método no lanza directamente una excepción, pero llama a un método que sí lo hace. Podemos elegir no controlar las excepciones por nosotros mismos y en vez de ello declararlo, como si fuera nuestro método el que actualmente lanza la excepción. SI no declaramos la excepción que puede que obtengamos de otro método, y no proveemos un try/catch para ello, entonces el método propagará al método que ha llamado a nuestro método, y puede ser capturado allí o continuar hasta ser controlado por un método en la parte mas inferior del stack.

    Cualquier método que peuda lanzar una excepción debe declarar la excepción. Esto incluye a los métodos que actualmente no la lanzan directamente, pero están pasando la excepción y dejando que la excepción pase mas abajo al siguiente método en el stack. Tenemos que recordar lo siguiente:

    Todo método debe controlar todas las excepciones comprobadas con una clausula catch o listar cada una de las excepciones sin controlar como una excepción que puede ser lanzada.

    Aunque de nuevo, algunas excepciones están fuera de esta regla. Un objeto del tipo RuntimeException puede ser lanzado desde cualquier método sin ser especificado como parte de la interface pública del método. Y incluso si un método declara una RuntimeException, el método que lo llama no tiene la obligación de controlar o declararlo. RuntimeException, Error, y todos sus subtipos son excepciones sin comprobar y las excepciones sin comprobar no están especificadas o controladas. Aquí un ejemplo:

    		import java.io.*;
    		class Test{
    			public int myMethod1() throws EOFException{
    				return myMethod2();
    			}
    			public int myMethod2() throws EOFException{
    				// código que actualmente podría lanzar la excepcion va aqui
    				return 1;
    			}
    		}
    		

    Vamos a echar un vistazo a myMethod1(). Ya que EOFException es subclase de IOException y IOException es subclase de Exception, es una excepción comprobada y debe ser declarada como una excepción que puede ser lanzada por este método. ¿Pero de donde vendría la excepción actualmente? La interface publica para el método myMethod2() llamado aquí declara una excepción de este tipo que puede ser lanzada. Si el método actualmente lanza la excepción por sí misma o llama a otro método que la lanza no es importante para nosotros; nosotros sabemos simplemente que tenemos que controlar al excepción o declarar cuando se lanza. El método myMethod1() no coge la excepción, por lo que solo declara que la lanza. Vamos a ver ahora otro ejemplo legal, myMethod3():

    		public void myMethod3(){
    			// Código que puede lanzar NullPointerException
    		

    De acuerdo con el comentario, este método puede lanzar un NullPointerException. Ya que RuntimeException es la superclase de NullPointerException, es una excepción sin comprobar y no necesita ser declarada. Podemos ver que myMethod3() no declara ninguna excepción.

    Las excepciones en tiempo de ejecución están referidas a las excepciones no comprobadas. Todas las otras excepciones son excepciones comprobadas, y no derivan de java.lang.RuntimeException. Una excepción comprobada debe ser controlada en algún lugar de nuestro código. Si invocamos un método que lanza una excepción comprobada pero no la controlamos en algún lado, nuestro código no compilará. Esto es por lo que son llamadas excepciones comprobadas; el compilador comprueba que todas ellas están manejadas o declaradas.

    Podemos también lanzar excepciones nosotros mismos, y estas excepciones pueden ser una excepción existente de la API de Java o una de nuestra propia. Para crear nuestra propia excepción, simplemente tenemos que crear una subclase que herede de Exception o algunas de sus subclases:

    		class MyException extends Exception{ }
    		

    Y si queremos lanzar la excepción, el compilador nos garantizará que la declaremos de la siguiente manera:

    		class TestEx{
    			void doStuff(){
    				throw new MyException(); // Lanza una excepcion comprobada
    			}
    		}
    		

    El código anterior trastornará al compilador.

    Tenemos que saber como un Error se compara con una excepción comprobada o sin comprobar. Los objetos del tipo Error no son objetos de Exception, aunque ellos representen condiciones excepcionales. Ambos Exception y Error comparten una superclase común, Throwable, por lo que ambos pueden ser lanzados usando la palabra clave throw. Cuando un Error o una subclase de Error es lanzado, está sin comprobar. No se requiere controlar los objetos de Error o de sus subtipos. Podemos tambien lanzar un Error por nosotros mismos, y lo podemos controlar, pero de nuevo, probablemente no queramos. Por ejemplo, ¿Qué haríamos si obtenemos un OutOfMemoryError? No es tan fácil como decirle al GC que se ejecute, podemos apostar que la JVM luchará desesperadamente para salvarse así mismo mientras nosotros recibimos el error. En otras palabras, no esperemos que la JVM en este punto diga “¿Ejecutar el GC? Oh, gracias por decirmelo. Jamás se me hubiera ocurrido. Claro, me pondré a ello.” Incluso mejor, ¿Que pasaría si se levanta un VirtualMachineError? Nuestro programa ya estaría tostado a la vez que nosotros recibimos el error, por lo que realmente no hay ningún punto en intentar coger alguno de estos errores. Solo recordemos, que podemos. Lo siguiente compilaría bien:

    		class TestEx{
    			public static void main (String[] args){
    				badMethod();
    			}
    			static void badMethod(){	// No tenemos que declarar el error
    				doStuff();
    			}
    			static void doStuff(){		// No tenemos que declarar el error
    				try{
    					throw new Error();
    				}
    				catch(Error me){
    					throw me; 	// lo cogemos, pero lo relanzamos
    		

    Si estuviéramos lanzando una excepción comprobada en vez de un Error, entonces el método doStuff() necesitaría declarar la excepción. Pero recordemos, ya que Error no es un subtipo de Exception, no necesita ser declarado. Somos libres de declararlo si queremos, pero al compilador no le importa una manera o otra de como el Error ha sido lanzado, o por quien.

  • Relanzando la misma Excepción

    Al igual que podemos lanzar una nueva excepción desde una clausula catch, podemos tambien lanzar esa misma excepción que hemos cogido. Aquí vemos una clausula catch que hace esto:

    		catch(IOException e){
    			// Hacemos cosas, entonces si lo decidimos podemos contlarlas
    			throw e;
    		}
    		

    Todas las demás clausulas actch asociadas con el mismo try son ignoradas, si existe un bloque finally, se ejecutará, y la excepción será lanzada al siguiente método en la pila de llamadas. SI lanzamos una excepción comprobada desde una clausula catch, debemos tambien declarar esa excepción. En otras palabras, debemos controlarla y declararla, en oposición a manejarla o declararla. El siguiente ejemplo es ilegal:

    		public void doStuff(){
    			try{
    				// operaciones IO de riesgo
    			} catch (IOException ex){
    				// no podemos manejarla
    				throw ex; // no podemos lanzarla a no ser que la declaremos
    			}
    		}
    		

    En el código anterior, el método doStuff() es claramente capaz de lanzar una excepción comprobada (en este caso una IOEXception), por lo que el compilador dice: “Bien, parece que tienes un try/catch aquí, pero no es lo suficientemente bueno. Si puedes relanzar la IOException que has cogido, entonces debes declararla”.


En esta entrada hemos visto como controlar excepciones mediante una estructura de código que Java nos proporciona, el try/catch, y como podemos usarlo para realizar otras operaciones mediante el finally. Además, hemos visto cual es la estructura de una excepción, cual es su jerarquía, como son lanzadas y cuando deben ser declaradas.

Sin mas, cualquier aporte o corrección es bienvenido.

Saludos!!!

  • El programador

    Demasiada información reunida aquí, Recien ando viendo excepciones y esto me ha caido demaravilla, gracias por el gran aporte. Saludos