6.3 Manejo de excepciones - ajpaez/OCA GitHub Wiki
(OCA Objectives 8.1, 8.2, 8.3, and 8.4)
Java proporciona un mecanismo para el manejo de errores que produce un código mas eficiente y organizado: el manejo de excepciones. El manejo de excepciones permite a los desarrolladores detectar errores fácilmente sin escribir código para comprobar los valores devueltos y aun mejor, nos permite mantener el código manejado por las excepciones limpio y separado del código que genera dichas excepciones.
El termino *"excepcion" * significa condición excepcional y es una suceso que altera el flujo normal del programa. Un monto de cosas puede llevar a una excepción, incluyendo errores de hardware, agotamiento de recursos y otros errores. Cuando se produce un evento unusual en Java, se dice que una excepción es lanzada. El código que es responsable de hacer algo con la excepción se denomina "exception handler" y es el encargado de hacer el "catch" a la excepción que se ha lanzado.
Capturando una excepción mediante try y catch
La manipulación de excepciones funciona transfiriendo la ejecución del programa a un excepction handler apropiado cuando se produce una excepción. Por ejemplo, si se llama a un método que abre un fichero pero el fichero no puede ser abierto, la ejecución del método se detendrá y se ejecutará el código que se escribió para tratar dicha situación. Para ello necesitamos indicar a la JVM que código ejecutaremos cuando ocurra una excepción, este código se encerrará entre las sentencias try y catch. El bloque try se utiliza para definir el bloque de código en el que las excepciones puede ocurrir. Este bloque de código es llamado "guarded region" y siempre debe ir acompañado de un try o un finally. El bloque de código que contiene puede lanzar una o mas excepciones de distinto tipo. La estructura de un bloque try y catch es la siguiente:
1. try {
2. // This is the first line of the "guarded region"
3. // that is governed by the try keyword.
4. // Put code here that might cause some kind of exception.
5. // We may have many code lines here or just one.
6. }
7. catch(MyFirstException) {
8. // Put code here that handles this exception.
9. // This is the next line of the exception handler.
10. // This is the last line of the exception handler.
11. }
12. catch(MySecondException) {
13. // Put code here that handles this exception
14. }
15.
16. // Some other unguarded (normal, non-risky) code begins her
Cada bloque catch tiene como requisito ir precedido de un bloque try. Ademas, todos los bloques catch deben ir precedidos unos de los otros.
Una vez que el control de la ejecución salta a un bloque catch, nunca se vuelve al bloque try. Una de las ventajas de usar el manejo de excepciones es que el código que se necesita para controlar el governed region solo debe ser escrito una vez.
Usando finally
Aunque try y catch proporciona un mecanismo excelente para capturar y manejar excepciones, nos queda el problema de qué hacer después de que ocurra una excepción. Dado que la ejecución se transfiere fuera del bloque try tan pronto como se genere una excepción, no podemos poner nuestro código de limpieza en la parte inferior del try y esperar que sea ejecutado si una excepción ocurre, sería tan mala idea como pensar en colocarlo dentro de los bloques catch. Ya que por mencionar una desventaja, habría que duplicar el código en cada catch. Java para evitar esto ofrece el bloque finally.
Un bloque finally incluye el código que siempre será ejecutado tras finalizar la ejecución del bloque try, independientemente de que se lance una excepción o no. Incluso si existe un return en un bloque try, el bloque finally se ejecuta justo despues de que se de que se encuentre dicho return y antes de que se ejecute el return.
Su estructura es la siguiente:
1: try {
2: // This is the first line of the "guarded region".
3: }
4: catch(MyFirstException) {
5: // Put code here that handles this exception
6: }
7: catch(MySecondException) {
8: // Put code here that handles this exception
9: }
10: finally {
11: // Put code here to release any resource we
12: // allocated in the try clause
13: }
14:
15: // More code here
El bloque finally no es requerido.
Ejemplos de bloques try, catch, finally
//try seguido de finally
try {
// do stuff
} finally {
// clean up
}
//try con un catch y seguido de finally
try {
// do stuff
} catch (SomeException ex) {
// do exception handling
} finally {
// clean up
}
//Try con errores, NO puede ir solo
try {
// do stuff
}
// need a catch or finally here
System.out.println("out of try block");
// Try con errores, NO se puede insertar código entre try y un bloque catch o finally
try {
// do stuff
}
// can't have code between try/catch
System.out.println("out of try block");
catch(Exception ex) { }
Propagando excepciones no capturadas
No es un requisito codificar las clausulas catch para cada posible excepción arrojada por los bloques try. Si un método no proporciona una clausula de catch para una excepción particular, este método se dice que está "ducking" (esquivando) la excepción o "passing de buck" (dando la responsabilidad a). Entonces, ¿ quá ocurre con la excepción esquivada? Cuando una excepción es esquivada, esta se lanza desde la parte superior ( o inferior, depende de la vista de cada uno) de la pila de llamadas y si no es capturada por ningún método a lo largo de las llamadas intermedias, la excepción lanzada llegará al último método en la parte inferior de la pila de llamadas. Esto se conoce como "exception propagation". Si una excepción alcanza el final de la pila de llamadas el programa explota ;). Una excepción que no es capturada nunca ocasiona que la aplicación se detenga. Se mostrará una descripción (si está disponible) de la excepción ocurrida y la pila de llamadas se descargará. Esto ayuda a depurar la aplicación indicado que excepción se ha lanzada, desde que método y que aspecto tenia la pila de llamadas en ese momento.
Definiendo excepciones
Todas las excepciones en Java son objetos. Cada excepción es una instancia de la clase Exception en la jerarquía de herencia. En otras palabras, las excepciones son siempre alguna subclase de java.lang.Exception. Cuando una excepción es lanzada, un objeto de algún subtipo de Exception es instanciado y entregado al exception handler como argumento para la clausula catch.
Jerarquía de excepciones
Todas las clases de excepciones son subtipos de la clase Exception. Esta clase hereda de la clase Throwable (que hereda de Object).
Como puedes ver, hay dos sublcases que heredan de Throwable: Exception y Error.
-
Las clases que heredan de Error representan una situación inusual que no es causada por errores del programa e indica que algo normalmente no ocurre sucedió durante la ejecución del programa, como que la JVM se quedo sin memoria, por lo general, una aplicación no podrá recuperarse de estas situaciones, por lo que no es necesario controlar estas excepciones. Los Errors no son técnicamente excepciones ya que no heredan de de la clase Exception.
-
Una Exception representa algo que ocurre no como un error de programación, sino porque algún recurso no esta disponible, o por cualquier otra condición requerida para la correcta ejecución del programa y que no está presente, por ejemplo, si una aplicación requiere comunicarse con otra aplicación y esta segunda no contesta.
Java proporciona muchas clases de excepciones, la mayoría tiene un nombre descriptivo. Existen dos formas para obtener información sobre una excepción . La primera es del tipo de excepción en si. La segunda es de la información que puede obtener del objeto excepción . La clase Throwable ( en la parte superior del árbol de herencia para excepciones) proporciona a sus descendientes algunos métodos que son útiles para manejadores de excepciones, como puede ser el método printStackTrace(), este método imprime el primer método y continua en la bajando en la pila de llamadas, imprimiendo el nombre de cada método a medida que se desplaza por la pila de llamadas ( esto se conoce como "unwinding the stack") desde la parte superior.
Manejo de una jerarquía de clases completa de excepciones
Como hemos dicho antes esta permitido especificar varias clausulas catch para los distintos tipos de excepciones esperadas. Se puede capturar mas de un tipo de excepción en una única clausula catch. Si la clase de la excepción especificada en el catch no tiene subclases, entonces solo la clase especificada por la excepción será capturada. Sin embrago, si la clase especificada en el catch tiene subclases, cualquier subclase del objeto especificado será capturado.
Por ejemplo, la clase IndexOutOfBoundsException tiene dos subclases, ArrayIndexOutOfBoundsException y StringIndexOutOfBoundsException. Es posible que desee escribir un manejador de excepciones que se ocupe de las excepciones producidas por cualquiera de los dos tipos de errores de limites, pero es posible que no se preocupe por la excepción que realmente se ha producido, en este caso bastará con escribir lo siguiente:
try {
// Some code here that can throw a boundary exception
}
catch (IndexOutOfBoundsException e) {
e.printStackTrace();
}
Si cualquier código en el bloque try lanza ArrayIndexOutOfBoundsException o StringIndexOutOfBoundsException la excepción será lanzada y capturada. Esto puede ser conveniente, pero debe usarse con moderación, usando una superclase de las clases lanzadas en la excepción estamos perdiendo información sobre la excepción.
Matching de excepciones
Si se tiene una jerarquía de excepciones compuesta por una excepción de superclase y algunos subtipos, y estas interesado en manejar una de los subtipos de una forma especial y el resto por separado solo es necesario escribir dos clausulas catch.
Cuando se lanza una excepción, java intenta buscar (entre los catch disponibles de arriba abajo) una clausa catch para el tipo de excepción. Si no encuentra una, buscará un manejador para el supertido de la excepción. Si no encuentra un catch que coincida con el supertipo para la excepciona, la excepción se propagará por la pila de llamadas. Este proceso de llama "exception matching". Vemos un ejemplo:
1: import java.io.*;
2: public class ReadData {
3: public static void main(String args[]) {
4: try {
5: RandomAccessFile raf =
6: new RandomAccessFile("myfile.txt", "r");
7: byte b[] = new byte[1000];
8: raf.readFully(b, 0, 1000);
9: }
10: catch(FileNotFoundException e) {
11: System.err.println("File not found");
12: System.err.println(e.getMessage());
13: e.printStackTrace();
14: }
15: catch(IOException e) {
16: System.err.println("IO Error");
17: System.err.println(e.toString());
18: e.printStackTrace();
19: }
20: }
21: }
Si este código genera una excepción de tipo FileNotFoundException, será manejada por el primer catch, si se genera otra de IOException (como puede ser EOFException) será manejada por el siguiente catch. Si se produce alguna excepción de cualquier otro tipo, está no será manejada y se relanzará en la pila de llamadas.
Observe como FileNotFoundException se colocó en primer lugar, es muy importante! Si lo hacemos al contrario, el programa no compilará.
Siempre deben de colocarse en primer lugar las excepciones mas especificas y posteriormente las mas generales.
Declaración de excepciones e interfaz publica
Entonces, ¿como reconocemos que un método lanza una excepción que tenemos que capturar? Así como un método debe especificar que tipo y cuantos argumentos acepta y que tipo de dato devuelve, las excepciones que un método puede lanzar deben de declararse en la firma de un método(a menos que las excepciones sean sublcases de RuntimeException).
La lista de excepciones lanzadas por un método es parte de la interfaz publica de este.
Estas excepciones se indican con la palabra clave throws y se utiliza de la siguiente forma:
void myFunction() throws MyException1, MyException2 {
// code for the method here
}
Este método tiene un void como valor devuelto, no acepta argumentos y declara que el puede lanzar excepciones de dos tipos distintos: MyException1 o MyException2. OJO! solo porque el método declare que lanza una excepción no significa que siempre se lanzará.
Puede darse el caso en el que un método nuestro haga uso de otro método que lance una excepción y decidimos que nuestro método no maneje dicha exception, en este caso la excepción se relanzará en la pila de llamadas. Cualquier método que pueda lanzar una exception ( a menos que sea una subclase de RuntimeException) debe de declarar la excepción. Esto incluye método que en realidad no la estén lanzando directamente, sino que las reenvíen dejando pasar la excepción al siguiente método en el pila. Las subclases de RuntimeExcepcion están exentan de que el compilador compruebe si se han declarado (unchecked) pero las no RuntimeException son consideradas excepciones checked y el compilador comprueba que se hayan declarado las excepciones que puedan relanzarse en los métodos intermedios.
Recuerda: Cada método debe manejar todas las excepciones checked mediante la clausula catch o listar cada una de las no manejadas mediante catch como una excepción relanzada, con ayuda de la declaración throw.
Esta regla se conoce en Java como "handle or declare" o "catch or declare"
Veamos esto en un ejemplo:
void doStuff() {
doMore();
}
void doMore() {
throw new IOException();
}
el método doStuff invoca a un método que lanza una excepción y este no maneja o declara dicha excepción. Estos casos son controlados por el compilador y nos informará de ellos. Los dos problemas que reconocerá son:
-
Primer, doMore() lanza una excepción checked pero no la declara, debemos reemplazar la firma del método por void doMore() throws IOException
-
El método doStuff() debe de redefinirse con dos alternativas: declarar la IOExeption (throws IOException) o manejar la excepción mediante un try/catch.
También se pueden creadas excepciones por nosotros mismos para ser lanzadas. Simplemente bastará con heredar de Exception, de la siguiente forma: class MyException extends Exception { } Y usar de la siguiente forma:
class TestEx throws MyException{
void doStuff() {
throw new MyException(); // Throw a checked exception
}
}
Necesitamos conocer como los objetos de tipo Error son comparados con excepciones checked o unchecked. Los objetos de tipo Error no son objetos de tipo Exception, ya que representan condiciones excepcionales. Tanto Exception como Error comparten una superclase común, Throwable, por lo tanto ambos pueden lanzarse con la palabra clave throw, Cuando se lanza un Error o una subclase suya (como RuntimeException), son unchecked, no requiere capturar dichos errores.
class TestEx {
public static void main (String [] args) {
badMethod();
}
static void badMethod() { // No need to declare an Error
doStuff();
}
static void doStuff() { // No need to declare an Error
try {
throw new Error();
}
catch(Error me) {
throw me; // We catch it, but then rethrow it
}
}
}
Relanzando la misma excepción
Así como, se puede lanzar una nueva excepción desde una clausula catch, tu puedes también lanzar la misma excepción que se ha capturado, de la siguiente forma:
public void doStuff() throws IOException {
try {
// risky IO things
} catch(IOException ex) {
// can't handle it
throw ex; // Can't throw it unless you declare it
}
}
Al hacer esto, todas las otras clausulas asociadas con el mismo try son ignoradas, y si existe un bloque finally este se ejecuta y la excepción es lanzado al método que invocó al método actual.