Control de flujo, Excepciones y Assertions

A continuación dejo el resumen del quinto capítulo (Flow Control, Exceptions, and Assertions) del libro "Sun Certified Programmer for Java 6 Study Guide".

Escribir código que utiliza los bloques if y switch (Objeto 2.1):
  • En un bloque if la única expresión legal es una booleana. Es decir, una expresión que pueda resolverse como un valor boolean o Boolean.
  • Tener cuidado con las asignaciones booleanas (=) ya que pueden ser confundidas con una comprobación de igualdad (==).
  • Los corchetes son opcionales en un bloque if que tenga solo una línea. Tener cuidado con la identación.
  • Un bloque switch solo puede evaluar enumeraciones (enums) o alguno de los tipos primitivos que puedan ser promovidos implícitamente al tipo int (byte, short, int o char).
  • Una constante case debe ser un literal o una variable final, o una expresión constante (incluido un enum). No puede existir una clausula case con una variable que no sea final, o con un rango de valores.
  • Si una condición en un bloque switch coincide con una constante case, la ejecución comenzará a partir de dicha constante case y continuará hasta que se encuentre con una instrucción break, o hasta la finalización del bloque switch. Es decir, la primer constante case que coincida será el punto de de comienzo de la ejecución.
  • La palabra clave default puede ser utilizada en un bloque switch si se quiere ejecutar algún código cuando ninguna de las constantes case aplican.
  • El bloque default puede estar situado en cualquier parte del bloque switch, es decir, antes, después o entre media de constantes case.
Escribir código que utilice bucles (Objetivo 2.2):
  • Un bloque básico for está compuesto por tres partes: declaración y/o inicialización, evaluación booleana, y la expresión de iteración.
  • Si una variable es incrementada o evaluada dentro de un bucle for, aquella debe estar declarada antes del este, o dentro de la declaración del bucle for.
  • Una variable declarada dentro de la sección de declaración de un bucle for solo puede ser utilizada dentro de este. Es decir, el ámbito de la variable es solo dentro del bucle for.
  • Se puede inicializar más de una variable del mismo tipo dentro de la sección de declaración del bucle for; cada inicialización debe estar separada por una coma.
  • Un bloque foreach (introducido en Java 6), está compuesto de dos partes, la declaración y la expresión. Es utilizado solo para iterar a través de los elementos de un arreglo o una colección.
  • En un for (de tipo foreach), la expresión es el arreglo o la colección a través de la cual se va a iterar.
  • En un for (de tipo foreach), la declaración es donde se declara la variable (la cuál es del mismo tipo que los elementos) que contendrá el valor de cada uno de los elementos de la colección.
  • Solo pueden ser utilizados valores o expresiones booleanas dentro de una condición de un if o un bucle. No se pueden utilizar números.
  • En el bucle do, su cuerpo siempre se ejecuta al menos una vez, incluso si la condición no se cumple.
Utilizar break y continue (Objetivo 2.2):
  • La clausula break provoca que la iteración actual de un bucle se detenga y que se ejecute la siguiente línea de código fuera del bucle.
  • La clausula continue provoca que la iteración actual de un bucle se detenga, luego se comprueba la condición del bucle, y en caso de ser afirmativa el bucle se ejecuta nuevamente.
  • Si una clausula break o una continue se encuentran etiquetadas, estas provocan la misma acción pero en el bucle etiquetado, no en el que se está ejecutando.
Manejando excepciones (Objetivos 2.4, 2.4 y 2.6):
  • Existen dos tipos de excepciones: comprobadas y no comprobadas.
  • Las excepciones comprobadas incluyen todos los subtipos de la clase Exception, salvo las subclases de RuntimeException.
  • Cualquier método que pueda lanzar una excepción comprobada la debe declarar en la clausula throws, o tratarla en los correspondientes bloques try/catch.
  • Los subtipos de Error y RuntimeException no son comprobados, por lo tanto el compilador no obliga a tratarlas o declararlas en la clausula throws.
  • Si se utiliza el bloque (opcional) finally, este será ejecutado tanto si el código del bloque try termina correctamente, como si se genera una excepción y la misma es atrapada por un bloque de tratamiento.
  • La única excepción en la que el bloque finally no será ejecutado, es si dentro del bloque try (o algún bloque catch) se realiza una invocación el método System.exit().
  • Que el bloque finally siempre se ejecute, no quiere decir que el mismo se ejecutará completamente. El código dentro de un bloque finally puede lanzar una excepción o invocar el método System.exit().
  • Las excepciones no atrapadas se propagan hacia abajo a través de la pila de llamadas (call stack). Comenzando a partir del método en el que la excepción fue lanzada y terminando en el lugar donde se trate la misma, o finalizando la ejecución del programa.
  • Se pueden declarar nuevas excepciones extendiendo de la clase Exception o alguno de sus subtipos. Estas excepciones son consideradas comprobadas y el compilador obliga a tratarlas o declararlas en la clausula thorws cada vez que sean generadas.
  • Los bloques catch deben estar ordenados desde los tipos de excepciones más específicos hacia los más generales (primero los subtipos y luego las clases bases). En caso contrario, el compilador generará un error porque algún bloque catch nunca va a poder ser alcanzado.
  • Las excepciones pueden ser creadas por la JVM o por un programador.
Trabajando con el mecanismo Assertions (Objetivo 2.3):
  • Una Assertion brinda una manera de testear las suposiciones durante el desarrollo y el debug.
  • Las Assertions típicamente están activadas durante el testeo pero desactivadas durante la implementación.
  • Se puede utilizar ‘assert’ como una palabra clave (a partir de la versión 1.4) o como un identificador, pero nunca de las dos maneras. Para compilar código antiguo que utilice la palabra ‘assert’ como un identificador, se debe usar el comando –source 1.3 a la hora de compilar.
  • El mecanismo Assertions por defecto esta desactivado en tiempo de ejecución. Para activarlo se debe utilizar el comando –ea o –enableassertions.
  • Para desactivar el mecanismo se debe utilizar el comando –da o –disableassertions.
  • Para activar o desactivar el mecanismo Assertions en general, se lo debe hacer sin ningún argumento. Se puede combinar la activación y desactivación para utilizar el alguna clase y/o paquete en especial.
  • No utilizar las assertions para validar argumentos de métodos públicos.
  • No utilizar expresiones assert que provoquen efectos. No se garantiza que las assertions siempre se van a ejecutar, y no se aconseja tener diferentes comportamientos dependiendo de si están o no activadas.
  • Utilizar assertions (incluso en los métodos públicos) para validar bloques de código que nunca van a ser alcanzados. Por ejemplo: assert false; Esto provoca un error en caso de que el código sea ejecutado.

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