Continuacion ejemplo de base de datos y reports con jasper report
Guía Java excepciones aserciones
1.
UNIVERSIDAD CATÓLICA ANDRÉS
BELLO
FACULTAD DE INGENIERÍA
ESCUELA DE INGENIERÍA INFORMÁTICA
Algoritmos y Programación III
Semestre 200922
Guía de excepciones y aserciones en Java
Elaborada por el Prof. Ricardo Casanova
rcasanov@ucab.edu.ve
2.
Introducción
Las excepciones no sólo son utilizadas en Java. Muchos lenguajes de programación
las utilizan como un mecanismo para indicarle al programa que debe hacerse en caso
de producirse un error inesperado en tiempo de ejecución, como por ejemplo invocar
un método con parámetros inválidos, división entre cero, etc.
Las aserciones, por otro lado, permiten realizar ciertas verificaciones sobre la lógica
del programa. Por ejemplo, si en algún punto determinado de la aplicación se presume
que el valor de una variable booleana pudiera ser true, la aserción permitiría
determinar si esto es cierto. Por lo general, las aserciones son utilizadas dentro de un
método para validar la lógica del mismo.
Ahora bien, ¿Cuál pudiera ser la diferencia entre validar la lógica del programa con
aserciones en lugar de utlizar bloques if como flags (banderas)?. En este caso, la
ventaja de utilizar aserciones es que las mismas pueden ser desactivadas al ejecutar
el código, permitiendo su habilitación al momento del desarrollo de la aplicación, pero
evitando la ejecución de las aserciones en tiempo de ejecución.
3.
Excepciones
Java permite manejar dos tipos de excepciones básicas: comprobadas y no
comprobadas.
Las excepciones comprobadas, como su nombre lo indica, permiten definir
excepciones que son comprobadas por el desarrollador y que éste debe gestionar en
el código. Estas excepciones por lo general se producen por situaciones externas a la
aplicación desarrollada, como por ejemplo, solicitar la apertura de un archivo que no
se encuentre en la ruta especificada.
Por otro lado, las excepciones no comprobadas proceden de errores a nivel del código
desarrollado. Estos errores suelen ser lo suficientemente complejos como para que el
código no pueda solventarlos de manera simple y de una forma razonable.
Las excepciones producto de problemas del entorno de los que es difícil recuperarse
se denominan errores. En este caso, los errores también son considerados
excepciones no comprobadas.
La clase Exception es la clase general que permite representar las excepciones
comprobadas y no comprobadas en Java. En este caso, al producirse una excepción,
en lugar de finalizar abruptamente la aplicación es más eficiente capturar la excepción
para controlarla.
También existe en Java la clase Error, utilizada para condiciones de error graves no
comprobadas y de las cuales se espera que la aplicación no se recupere.
La clase RuntimeException es la clase general utilizada para las excepciones no
comprobadas que pueden derivarse de los defectos del programa.
Ejemplo de una excepción
El siguiente código muestra un programa que permite generar una sumatoria de todos
los elementos pasados como parámetros en la línea de comandos:
public class Excepcion
{
public static void main(String [] args)
{
int i, sum = 0;
for (i=0 ; i<args.length ; i++)
sum += Integer.parseInt(args[i]);
System.out.println("Sumatoria = " + sum);
}
}
Esta clase puede generar una excepción. En prinicipio, el código funcionaría
perfectamente si todos los parámetros fueran enteros. Por ejemplo:
4.
java Excepcion 1 2 3 4 5
Sumatoria = 15
En este caso, si alguno de los argumentos no fuera entero, la aplicación fallaría, como
por ejemplo:
java Excepcion 1 dos 3 4 5
Generará la siguiente excepción:
Exception in thread "main" java.lang.NumberFormatException: For input string: "dos"
at
java.lang.NumberFormatException.forInputString(NumberFormatException.java:48)
at java.lang.Integer.parseInt(Integer.java:447)
at java.lang.Integer.parseInt(Integer.java:497)
at Excepcion.main(Excepcion.java:21)
Cómo capturar una excepción. Bloque try - catch
Java proporciona un mecanismo para capturar la excepción que se produce e
identificar de qué tipo es (es heredado de C++). Este mecanismo es el bloque try-
catch. El siguiente código muestra como utilizar el bloque:
public class Excepcion
{
public static void main(String [] args)
{
int i, sum = 0;
try
{
for (i=0 ; i<args.length ; i++)
sum += Integer.parseInt(args[i]);
System.out.println("Sumatoria = " + sum);
}
catch(NumberFormatException e)
{
System.out.println("ERROR en uno de los argumentos");
}
}
}
De este manera al producirse una excepción en el bloque try, automáticamente se
saltará al bloque catch, con el fin de identificar el tipo de excepción generada.
Manejo preciso de excepciones
Ya que el manejo de excepciones permite que la aplicación se siga ejecutando, es
ideal identificar en cuáles bloques precisos de código es necesario colocar un try-
catch. En el ejemplo anterior, sería deseable que la aplicación omitiera las valores
incorrectos, pero que procesara los que efectivamente pudieran ser convertidos a
5.
enteros. El siguiente código muestra un uso mucho más eficiente del manejo del try-
catch:
public class Excepcion
{
public static void main(String [] args)
{
int i, sum = 0;
for (i=0 ; i<args.length ; i++)
{
try
{
sum += Integer.parseInt(args[i]);
}
catch(NumberFormatException e)
{
System.out.println("ERROR en el argumento args[" + i + "]");
}
}
System.out.println("Sumatoria = " + sum);
}
}
En este caso, la aplicación capturará la excepción de aquellas posiciones del arreglo
que no puedan ser convertidas al valor entero, pero ejecutará la suma para aquellos
casos en los que los valores sean correctos, imprimiendo al final la suma de éstos
términos.
Utilización de múltiples cláusulas catch
Por cada bloque try necesariamente debe haber al menos un bloque catch. En
algunos casos sería interesante incluir múltiples bloques catch luego de un try, de
forma tal de poder capturar varias excepciones y de esta manera “blindar” aún más
nuestra aplicación, tal como se muestra a continuación:
try
{
//Código para tratar de ser ejecutado
}
catch (MiException e1)
{
//Código para manejar la excepción e1
}
catch (MiSegundaException e2)
{
//Código para manejar la excepción e2
}
catch (MiTerceraException e3)
{
//Código para manejar la excepción e3
}
6.
catch (MiCuartaException e4)
{
//Código para manejar la excepción e4
}
En este caso, el orden en que se coloquen los bloques catch tiene una importancia, ya
que al momento de producirse una excepción en el bloque try, se ejecutarán los
bloques catch de acuerdo al orden en el que fueron implementados en el codigo.
Apilamiento de las llamadas
Es importante aclarar que al momento de producirse una excepción en un método, si
éste no gestiona dicha excepción, la misma es enviada directamente al método que
efectuó la llamada. Si éste tampoco es capaz de gestionar la excepción, se devuelve
al método inmediatamente anterior y así sucesivamente. Si al momento de llegar al
bloque main, éste no es capaz de gestionar la excepción, la aplicación finaliza de
forma irregular.
Cláusula finally
La clásula finally define un bloque de código que siempre se ejecutrá,
independientemente que se produzca una excepción o no, tal como se muestra en el
siguiente ejemplo:
public class Excepcion
{
public static void main(String [] args)
{
int i, sum = 0;
i = 0;
while (i<args.length)
{
try
{
sum += Integer.parseInt(args[i]);
}
catch(NumberFormatException e)
{
System.out.println("ERROR en el argumento args[" + i + "]");
}
finally
{
i++;
}
}
System.out.println("Sumatoria = " + sum);
}
}
Este ejemplo es una modificación de los vistos anteriormente, manejando en este caso
un ciclo while para recorrer el arreglo de argumentos. Independientemente que de
7.
produzca una excepción o no, es necesario incrementar el valor de iterador i, de forma
tal de poder recorrer todo el arreglo. Un error común en este código sería colocar el
incremento de i en el bloque try. Al momento de producirse una excepción, se saltaría
directamente al bloque catch sin haber incrementado aún el valor de i, lo que producirá
un ciclo infinito (ya que nunca se incrementará la posición donde se produjo la
excepción).
Categorías de la excepciones
En el caso de Java, la clase java.lang.Throwable actúa como la clase general para
todos los objetos que pueden enviarse y capturarse utilizando los mecanismos de
control de excepciones. Existen tres subclases de la excepción Throwable: Error,
RuntimeException y Exception, tal como se muestra a continuación:
8.
Excepciones más comunes
NullPointerException: Se produce cuando se trata de acceder a un atributo o método
de un objeto asociado a una variable que no hace referencia real a ningún objeto. Por
ejemplo, cuando el objeto se utiliza sin haber sido inicializado, tal como se muestra a
continuación:
Gerente miGerente = null;
System.out.println(miGerente.getNombre());
FileNotFoundException: Se genera cuando existe un intento de leer datos de un
archivo que no existe en el path especificado.
NumberFotmatException: Se produce cuando se trata de convertir una cadena en un
número (puede ser entero o real) que tiene un formato de número inválido.
ArithmeticException: Se genera al tratar de dividir un número entre cero.
SecurityException: Por lo general se genera en navegadores, cuando existen applets
que tratan de ejecutar operaciones que pudieran ser peligrosas para el host o los
archivos localizados en él.
Cómo realizar la gestión de la excepción
Java establece que de producirse una excepción, el método donde se produjo debe
definir de forma explícita que acción realizar para controlar dicha excepción. En este
caso existen dos formas para hacer esto:
Gestionar la excepción a través del bloque try-catch-finally: Se hace que el método
gestione la excepción delegando su control en la excepción que se defina en el bloque
catch.
Declarar de antemano las excepciones que un método puede generar: En este caso,
es el método el encargado de determinar que acción tomar ante la aparición de una
excepción. Para esto, el cuerpo del método puede incluir una clásula thows de la
siguiente manera:
void metodo() throws IOException
{
…..
}
Así mismo es posible establecer una serie de excepciones que pueden ser generadas
dentro de un método, separando cada excepción por una coma después de la cláusula
throws, como por ejemplo:
void metodo() throws IOException, SegundaExcepcion, TerceraExcepcion
{
…..
}
9.
Sobrescritura de métodos y excepciones
Cuando sobrescribimos un método que genera excepciones, el método que
sobrescribe sólo puede declarar excepciones que fueran de la misma clase o subclase
de las excepciones definidas en el método original. En este caso, es posible declarar
menos o más excepciones específicas en la clásula throws del método, siempre y
cuando se conserve la regla definida al inicio.
En el siguiente ejemplo se declaran tres clases: TestA, TestB y TestC, siendo TestA la
superclase de TestB y TestC.
public class TestA
{
public void metodo() throws IOException
{
//Codigo del metodo
}
}
public class TestB extends TestA
{
public void metodo() throws EOFException
{
//Codigo del metodo
}
}
public class TestC extends TestA
{
public void metodo() throws Exception
{
//Codigo del metodo
}
}
La clase TestB compila debido a que EOFException es una subclase de IOException.
Sin embargo, la clase TestC no compilará ya que Exception es un superclase de
IOException.
Aún así es válido declarar métodos de sobrescritura que generen menos excepciones
que el método de la superclase e incluso que no generen ninguna excepción.
10.
Creación de excepciones propias
Java permite que el desarrollador cree sus propias excepciones, escribiendo para ello
clases que amplíen la clase Exception, como se muestra a continuación:
public abstract class Animal
{
protected int legs;
protected Animal()
{
}
protected Animal(int legs)
{
this.legs = legs;
}
public void walk()
{
System.out.println("Este animal camina sobre " + legs + " piernas");
}
public abstract void eat();
}
public class NumLegException extends Exception
{
public NumLegException(String mensaje)
{
super(mensaje);
}
}
public class Cat extends Animal implements Pet
{
private String name;
public Cat(String n, int numeroPatas) throws NumLegException
{
super();
if (numeroPatas == 4)
{
legs = numeroPatas;
name = n;
}
else
throw new NumLegException("El gato debe tener 4 patas!!!");
}
11.
public Cat(int numeroPatas) throws NumLegException
{
super();
if (numeroPatas == 4)
{
legs = numeroPatas;
}
else
throw new NumLegException("El gato debe tener 4 patas!!!");
}
public String getName()
{
return name;
}
public void setName(String n)
{
name = n;
}
public void play()
{
System.out.println("Cat plays");
}
public void eat()
{
System.out.println("Cat eats");
}
}
public class TestAnimals
{
public static void main(String [] args)
{
Fish miFish = new Fish();
Cat miCat;
miFish.eat();
try
{
miCat = new Cat("Tom", 6);
miCat.eat();
}
catch(NumLegException nle)
{
System.out.println("Error: " + nle.getMessage());
}
}
}
12.
En este caso se define una clase Animal de tipo abstracta y una clase Cat que
extiende de dicha clase e implementa el método abstracto comer. Adicionalmente se
define la excepcion numLegException que extiende de la clase Exception. La clase
Cat define el uso de esta excepción, validada al momento de asignar el número de
patas del animal en el constructor. Si el número de patas no es igual a 4, se dispara la
excepción.
En la clase TestAnimals de define la inicialización del objeto miCat dentro de un
bloque try-catch. Al dispararse la excepción por tratar de asignarle 6 patas al animal, la
excepción disparada es capturada en el bloque catch, imprimiendo el mensaje
respectivo. En este caso, el método getMessage es heredado de la clase Exception y
permite visualizar la razón por la cual se disparó la excepción.
Aserciones
Las aserciones admiten dos tipos de sintaxis:
assert <expresion_booleana> ;
assert <expresion_booleana> : <expresion_detallada> ;
En cualquier caso, si la expresión booleana evaluada es falsa, se genera un error de
aserción (AssertionError). Este tipo de error no debería capturarse y el programa
debería finalizar de forma anormal.
En caso de utilizarse el segundo formato, la segunda expresion (que puede ser de
cualquier tipo) será convertida al tipo String y se utilizará para complementar el
mensaje a mostrar por pantalla al momento de producirse el AssertionError.
Uso recomendado de las aserciones
Las aserciones permiten generar información acerca de las supocisiones y
expectativas que maneje el desarrollador al momento de programar. Por tal motivo, las
aserciones resultan de alta utilidad al momento de trabajar con el código para realizar
operaciones de mantenimiento.
En líneas generales, las aserciones deben utilizarse para verificar la lógica de un sólo
método o de un pequeño conjunto de métodos que estén relacionados entre sí. No
deben utilizarse para comprobar todo el código desarrollado sino para verificar que se
cumplan las condiciones internas.
He aquí algunos ejemplos del uso adecuado de aserciones: invariantes internas,
invariantes de flujo de control, las postcondiciones y las invariantes de clase.
Invariantes internas
Las invariantes internas se producen cuando se desarrolla un código asumiendo que
una determina situación se producirá siempre en todos los casos o en ninguno y se
escribe el código de acuerdo con dicha convicción, como por ejemplo en el siguiente
código:
13.
if (x > 0)
{
//Codigo dentro del bloque if
}
else
{
//Codigo dentro del bloque else
}
Si se parte de la premisa que la variable x puede tomar valores positivos (condición
del if) y un valor igual a cero (condición del else) y nunca un valor negativo, en caso de
ocurrir esto último, el código ejecutará el bloque else, produciendo resultados
inesperados para esta condición. Ya que el código nunca posee un segmento de
validación para este caso, el error, de producirse, será detectado una vez el daño esté
hecho.
Para estos casos, la utilización de las aserciones resultan de gran utilidad, ya que
permitirían garantizar la veracidad de la premisa, tal como se muestra a continuación:
if (x > 0)
{
//Codigo dentro del bloque if
}
else
{
assert (x == 0);
//Ejecutar este código a menos que x sea negativo
}
Invariantes del flujo de control
Las invariantes del flujo de control manejan casos parecidos a las invariantes internas,
pero en este caso estas invariantes tienen más que ver con la dirección que sigue el
flujo del programa que con la veracidad o no de una premisa. Por ejemplo, es posible
llegar a pensar que se han contemplado todos los casos posibles para una sentencia
switch y que por tanto nunca se llegará a ejecutar la sentencia default. Esto puede ser
validado fácilmente agregando una aserción en el código, tal como se muestra a
continuación:
String nombre = "";
switch(palo)
{
case PICAS:
nombre = "Picas";
break;
case DIAMANTES:
nombre = "Diamantes";
break;
case CORAZONES:
nombre = "Corazones";
break;
14.
case TREBOLES:
nombre = "Treboles";
break;
default:
assert false : "Palo desconocido en la baraja";
}
Postcondiciones e invariantes de clase
Las postcondiciones son supocisiones sobre el valor o las relaciones que presentan
las variables al finalizar un método. Para ilustrar este caso, un ejemplo sencillo sería el
de una pila y el método de desapilamiento (pop), para validar si luego realizar la
operación de eliminación, la pila cuenta con un elemento menos que al inicio del
método (a menos que la pila ya estuviera vacía). El código pudiera ser algo como lo
siguiente:
public Object pop() throws RuntimeException
{
int size = this.getElementCount();
if (size == 0)
{
throw new RuntimeException("Intento de desapilar una pila vacia");
}
Object result = /* Codigo para recuperar el elemento desapilado */
/* Probar la postcondicion */
assert (this.getElementCount() == size - 1);
return result;
}
Es importante notar que en caso de no producirse la excepción al tratar de eliminar un
elemento de una pila vacía, la aserción no tendría ninguna validez en este caso, ya
que el tamaño de la pila vacía (cero) no permitiría la ejecución de la aserción. Así
mismo es importante hacer notar que en el caso de trabajar con una pila vacía se
dispara una excepción y no una aserción, ya que en caso de que se ingrese al bloque
del if ello no tiene nada que ver con una falla en la lógica de la aplicación. En general,
las validaciones sobre el comportamiento de la lógica de la aplicación deberían ser
validadas con excepciones, no con aserciones. Esto también garantiza que la
validación se hará siempre y no se deshabilitará, como si puede ocurrir con las
aserciones.
Por otro lado, una invariante de clase es aquella que puede comprobarse al final de
cada llamada a un método de una clase. En el caso de una clase pila, una condición
invariante de clase pudiera ser aquella en la que el número de elementos nunca es
negativo.
15.
Control de la evaluación de las aserciones en tiempo de ejecución
Una de las principales ventajas del uso de aserciones frente a las excepciones es que
las primeras pueden ser desactivadas durante el tiempo de ejecución. Las aserciones
en Java están desactivadas de forma predeterminada. Para activarlas, es posible
utilizar alguna de las siguientes formas:
java –enableassertions MiPrograma
java –ea MiPrograma
Así mismo, es posible desactivar las aserciones utilizando la siguiente sintaxis:
java –disableassertions MiPrograma
java –da MiPrograma