Tratamiento de errores mediante excepciones

Resumen del capítulo 12 (Tratamiento de errores mediante excepciones) del "Libro Thinking in Java (4ta Edición)".

Introducción

Para crear un sistema robusto, cada componente tiene que ser robusto. Al proporcionar un modelo coherente de informe de errores utilizando excepciones, Java permite que los componentes comuniquen los problemas de manera fiable al código cliente. Imponer esta formalidad para el tratamiento de errores permite crear sistemas de gran envergadura utilizando menos código de lo habitual y reducir la complejidad del mismo.
Una excepción se genera cuando ocurre una situación inesperada, lo cual impide continuar con el normal procesamiento, ya que no se posee la información necesaria para tratar con el problema en el contexto actual.
Sucesos que se dan a partir de la generación de una excepción:
  1. Se crea un objeto excepción de la misma manera que cualquier otro objeto (utilizando la instrucción new).
  2. Se detiene la ruta actual de ejecución y se extrae del contexto actual la referencia al objeto excepción.
  3. Luego el mecanismo de tratamiento de excepciones se hace cargo del problema y comienza a buscar un lugar apropiado donde continuar ejecutando el programa.
  4. Dicho lugar es la rutina de tratamiento de excepciones, cuya tarea consiste en recuperarse del problema de modo que el programa pueda intentar hacer otra cosa o simplemente continuar con lo que estuviera haciendo.
Existen tres secciones importantes a la hora de utilizar el mecanismo de excepciones:
  • Región protegida (o bloque try): Este bloque de código es un ámbito de ejecución ordinario, salvo que dentro del mismo se incluye código que podría generar excepciones. Es decir, lo que se hace es “probar” el código (generalmente invocaciones a métodos).
  • Rutina de tratamiento: Cuando se genera una excepción en el bloque try, esta se debe tratar en algún lugar. Este lugar son las rutinas de tratamiento. Generalmente existe una rutina de tratamiento para cada tipo de excepción que pueda ser generada en el bloque try (esto permite actuar en consecuencia de la generación de diversos tipos de errores). Estas rutinas están situadas luego del bloque try y se denotan mediante la palabra clave catch. Solo se ejecutará una sola clausula catch, la que se ajuste al tipo de excepción generada.
  • Finalización (o bloque finally): Es un bloque de código que se ejecuta siempre, más allá de que se haya generado una excepción, o no se haya hecho. Esta sección generalmente se utiliza cuando es necesario restaurar a su estado original alguna otra cosa distinta de la propia memoria. (Es importante recalcar que la ejecución de este bloque de código se realiza SIEMPRE, más allá de que dentro del bloque try haya una instrucción return.)
Ver ejemplo: Main01.java

Excepciones definidas por el usuario

La jerarquía de excepciones de Java no puede prever todos los errores de los que vayamos a querer informar, por eso se pueden crear excepciones definidas por el usuario que indican errores especiales para una biblioteca. Para crear excepciones definidas por el usuario, se debe heredar de una clase de excepción existente, preferiblemente de una cuyo significado esté próximo al de nuestra propia excepción (aunque a menudo esto no es posible). Lo más importante acerca de una excepción es el nombre de la clase, que hace referencia al tipo de error.
Ver ejemplo: codigo/Main02.java

Especificación de excepciones

Mientras se ejecuta un método se puede dar la situación de que se genere una excepción, ante este escenario, se pueden realizar dos cosas. El propio método puede contener una rutina de tratamiento para dicha excepción o que el mismo lance la excepción a un contexto superior. En el segundo caso, la excepción es lanzada al contexto en donde el método fue invocado. Esto significa que la invocación de un método puede generar una excepción (por lo cual la invocación debe estar dentro de un bloque try).
Java proporciona una sintaxis (y es obligatorio utilizarla) para permitir especificar las excepciones que puede generar un método. Se trata de la especificación de excepciones, la cual forma parte de la declaración del método y aparece luego de la lista de argumentos. Utiliza la palabra clave throws y contiene todos los tipos potenciales de excepciones que puede generar el método. El compilador provocará un error en caso de que se intente lanzar una excepción (de tipo comprobada) no declarada en la clausula throws.
NOTA: Las excepciones no comprobadas (excepciones que heredan de la clase RuntimeException o algún subtipo de ella) no se necesitan declarar en la clausula throws. Es obligatorio declarar todas las excepciones comprobadas potenciales que puede lanzar un método.
Ver ejemplo: codigo/Main03.java

La clase Throwable describe todas las cosas que pueden generarse como una excepción. Existen dos tipos generales de objetos Throwable (que heredan de ella). El subtipo Error representa los errores de tiempo de compilación y del sistema de los que no tenemos que preocuparnos de capturar. El subtipo Exception es el tipo básico que puede generarse desde cualquiera de los métodos de la biblioteca estándar de Java.
Existe un conjunto completo de tipos de excepción que son generadas de forma automática por Java y que no es necesario incluir en las especificaciones de excepciones (como se mostro en el ejemplo de código anterior Main03.java). Estas excepciones están agrupadas y son subtipos de la clase RuntimeException. Son denominadas excepciones no comprobadas. Las mismas indican errores de programación, normalmente no se suelen capturar, ya que el sistema las trata automáticamente.

Las excepciones que se necesitan saber para el exámen son:
Derivadas de RuntimeException:
  • ArrayIndexOutOfBoundsException: Excepción que hereda de IndexOutOfBoundsException. Se lanza cuando se intenta acceder a un elemento de un arreglo con un índice inválido o con un valor negativo.
  • ClassCastException: Se lanza cuando se intenta castear una variable de referencia a un tipo que no cumple con la relación ES-UN.
  • IllegalArgumentException: Se lanza cuando un método recibe un argumento formateado diferente a como el método lo espera. Por ej.: el método parseInt() genera esta excepción.
  • IllegalStateException: Se lanza cuando el estado de un ambiente no es el correcto para la operación que se esta intentando. Por ej.: utilizar un objeto Scanner que esta cerrado.
  • NullPointerException: Se lanza cuando se intenta acceder a un objeto con una variable de referencia con valor null.
  • NumberFormatException: Excepción que hereda de IllegalArgumentException. Se lanza cuando un método que convierte un String a un número, recibe un String que no puede ser convertido.
Derivadas de Error:
  • AssertionError: Se lanza cuando cuando la expresión dentro de un assert tiene el resultado false.
  • ExceptionInInitializerError: Hereda de LinkageError. Se lanza para indicar que ha ocurrido una excepción al momento de inicializar una variable estática o un bloque de inicialización estático.
  • StackOverflowError: Hereda de VirtualMachineError. Se lanza cuando se produce un desbordamiento de la pila. Esta situación se da debido a un método que recursa profundamente.
  • NoClassDefFoundError: Hereda de LinkageError. Se lanza la JVM no puede encontrar la definición de una clase que necesita. Sus causas pueden ser: un error al especificar la clase en la línea de comandos, un tema relacionado al classpath, o la falta del archivo .class.

Restricciones de la excepciones

Cuando sustituimos un método, sólo podemos generar aquellas excepciones que hayan sido especificadas en la versión del método correspondiente a la clase base. Esta restricción implica que el código que funcione con la clase base funcionará también automáticamente con cualquier objeto derivado de la clase base.
En cambio los constructores pueden generar todas aquellas excepciones que deseen, independientemente de lo que genere el constructor de la clase base. Sin embargo, puesto que siempre hay que invocar algún constructor de la clase base, el constructor de la clase derivada deberá declarar todas las excepciones del constructor de la clase base en su propia especificación de excepciones.
En resumen, la interfaz de especificación de excepciones de un método concreto puede estrecharse durante la herencia y cuando se realizan sustituciones, pero nunca ensancharse (solo puede ensancharse con excepciones derivadas a la declarada en el método de la clase base ver ejemplo). En cambio en un constructor de una clase derivada, la interfaz de especificación de excepciones puede ensancharse pero no estrecharse.
Ver ejemplo: codigo/Main04.java

2 comentarios:

Hana Lara dijo...


Hola!

Muchas gracias por la información que publicas, a mi me ha sido de mucha utilidad y supongo que a muchos otros también :D

Saludos!

Matías Emiliano Alvarez Durán dijo...


Muchas gracias! Hasta ayer lo tenía medio olvidado al blog, pero ya lo retome!!!
Saludos ;)



Publicar un comentario

Este blog dejo de ser mantenido el día 12/02/2010. Para cualquier consulta o comentario realizarlo a través del sitio http://onj2ee.blogspot.com/