Unidad 4: "Gestión de
errores y excepciones"
Objetivos de la
Unidad:
- Repasar los principios de
control y recuperación de errores
- Noción de excepción
- Gestión de
excepciones
- Cláusulas try, catch, throw, throws y
finally
- Jerarquía de clases
standard de la libreria Java
- Creación de excepciones
propias
Control y recuperación de
errores
- El sistema tradicional de
control de errores como el empleado habitualmente en el Lenguaje C consiste en
analizar el valor de retorno de cada una de las funciones llamadas para
comprobar si ésta se ha ejecutado correctamente.En caso de
problemas el valor devuelto es un código de error.
- El sistema de excepciones consiste
en un mecanismo que permite que el tratamiento del error pueda llevarse a cabo
en un nivel superior en la posición de llamadas de funciones, no siendo
obligatorio que lo haga el módulo inmediatamente superior (que es lo que la ha
llamado). Podría entenderse como unos largos goto
entre módulos (siempre hacia arriba)
- Dicho sistema engloba al anterior y
lo amplía, y viene siendo utilizado (con sus variantes) con lenguajes como
Visual Basic (son los On Error), C++ (muy similar a Java) y otros.
- Las excepciones permiten un
tratamiento centralizado de los errores y permite salvo excepciones
reducciones drásticas de código suplementario destinado al tratamiento y
recuperación de los mismos.
- En todo este asunto tan importante o
más que la detección del error es la recuperación del mismo, es decir que el
programa informe del porqué no deja realizar cierta operación y que además en
lo posible permita realizar por lo menos las otras no afectadas por el
problema anterior.
- No debería ser admisible informar de
un error y "echar" al usuario al sistema operativo, aunque sea lo más
fácil.
- Se estima que un programa sin
control y recuperación de errores se multiplica entre 2 y 3 al añadir un
sistema clásico, con lo que dificulta la legibilidad del código. Mediante las
excepciones la solución es mucho más limpia.
Control de errores clásico
- Este sistema plantea el
inconveniente que se ilustra a continuación: Supongamos un diagrama jerárquico
de llamadas a funciones clásico:
main()
+----- f()
+----- h()
+------
j()
+----- i()
+----- g()
- main() en algún momento llama a f() y a g().A su vez, f() llama a h() y a i().Finalmente,
j() es llamado en algún momento por h().
- Ante este escenario, supongamos que
main() es el menú principal y que f() representa a una de las funciones principales del programa,
que a su vez contiene diversas sub-funciones.
- Supongamos que en j() se produce un error tal como no encontrar cierto fichero en
concreto y que ello le impide concluir la operación que en realidad se ha
iniciado en main().
- Con este modelo, j() debe transmitir a h() el
código de error, h()
debe hacer lo mismo
con f() y finalmente f() lo
mismo con main().
- Finalmente a main(), f() le devuelve un código que le indica que en
j() no se ha encontrado el fichero "tal". Lo correcto sería mostrar un mensaje, inteligible o no
para el usuario pero que por lo menos permita a los informáticos (via
teléfono, e-mail, etc) identificar dónde se ha producido el error y su
causa.
- Se observa que el nivel de
acoplamiento entre módulos es elevado, puesto que toda la cadena de funciones
desde donde se produce el error hasta el punto en que se quiera tratar (en
este caso main()) debe saber cómo
propagar los posibles errores que se produzcan debajo de ellos hacia
arriba.
- En definitiva, los módulos no son
independientes entre sí el sobrecoste en líneas de código adicionales para el
control y recuperación de errores puede ser muy alto.
Control de errores por
excepciones
- La idea fundamental es que
la condición de error hay que detectarla como siempre (un if(condición) else
...) en el lugar concreto donde se produzca (En nuestro ejemplo enj() no se puede abrir un fichero y por ejemplo podría detectarse
por el código de retorno de una función del tipo open).
- A partir de aquí el problema es cómo
informar del error hacia arriba. Para ello Java proporciona la palabra
reservada del lenguaje throw.
- Según el ejemplo, j() podría ser algo como:
void j()
{
...
if(no se
puede abrir el fichero)
{
throw new Exception("El
fichero tal no se puede abrir");
}
...
}
- throw que significa "lanzar" actúa por una vía
distinta que el return.
- Para capturar lo que envía un
throw hay que interponer una sentencia catch en algún punto en el camino del camino desde j() hacia arriba en la jerarquía de llamadas a funciones.
- Si catch se coloca en el propio
j() estamos como en el sistema clásico. Sin
embargo si se coloca en main(), todos los errores
que se produzcan por debajo serán capturados en el catch
de main().
- El catch en main() podría tener el
siguiente aspecto:
main()
{
...
catch(Exception e)
{
System.out.println(e.toString());
}
...
}
- En una situación real no siempre
será tan simple como capturar directamente todos los errores en main() y seguir la ejecución. Supongamos que h() abre un fichero. Cuando se produce la excepción
en j(), antes de ir directamente a main() hay que pasar por h() y
restituir las condiciones de "aquí no ha pasado nada", en este caso cerrar el
fichero.
- Para ello se interpone un
catch() en h(), se
cierra el fichero y se "relanza" de nuevo hacia arriba.
Clasificación de las
excepciones
- La clase Exception es standard de
Java. En realidad Java proporciona una estructura básica con unos cuantos
tipos básicos de excepciones standard.
- Esta estructura de clases arranca
con una clase base Throwable de la que
derivan Error y Exception. De esta última deriva RuntimeException
- El diagrama queda así:
Throwable
+----- Error
+-----
Exception
+-----
RuntimeException
- La diferencia entre un Error y una Exception es que los
primeros no son recuperables (por ejemplo un error de hardware).
- Las excepciones definidas como
subclases de RuntimeException no son de
obligado tratamiento. Muchas excepciones son consideradas tan potencialmente
obvias como impredecibles como NullPointerException que
se produce al llamar un método de una referencia a objeto no definida, o como
ArithmeticException que se
produce al dividir un entero por cero. El tener que contemplarlas de modo
obligado podría llegar a ser irritante por los programadores Java tal como
indica James Gosling en "The Java Language Specification".
- Si las excepciones subclases de
RuntimeException
no se tratan éstas llegan hasta
la máquina virtual y el hilo de ejecución (thread) afectado por la excepción
se termina.
- Si se tratan (con un try-catch o
dejándolas salir del método llamante) entonces son como una excepción
normal.
- En el ejemplo siguiente se muestra
como se podría evitar una detención inesperada
- import
java.io.*;
class NoPasaNada
{
public static void
main(String args[])
{
for (int i =
0; i < 10;
i++)
{
funcion();
System.out.println("Aqui
no pasa
nada");
}
}
static
void
funcion()
{
try
{
Rectangulo r0 =
null;
r0.area(); // provoca una
NullPointerException (que deriva de
RuntimeException)
}
catch
(RuntimeException e){}
}
}
- El ejemplo anterior es para
responder a la pregunta cómo se podría evitar técnicamente en Java la
interrupción del hilo de ejecución. No obstante si se ha producido una
excepción es que algo pasa y seguramente más adelante en la ejecución puede
llegar un momento en que el programa manifieste otras situaciones de
excepción. Las excepciones son para tratarlas y no para hacer ver que no pasa
nada.
- En Java es obligatorio tratar las
excepciones que derivan directamente de Exception tal y como IOException. Por ello la
función System.in.read()
requiere el tratamiento de la
excepción.
- Hay dos modos de tratar las
excepciones: Una es incluir la llamada en cuestión (como System.in.read()) en un bloque try catch. La segunda es declarar que nuestra función (la que llama a System.in.read()) relanza hacia arriba toda excepción que pueda
producirse en su interior añadiendo al final de su signatura ... throws Exception. (Ver Ud2).
- Con el fin de contemplar varias
sub-clases de Exception pueden
concatenarse varios catch()seguidos. Nosotros
como programadores podemos ampliar la jerarquía de clases básica que nos
ofrece el standard de Java. Ver ejemplo:
- // Creación de una
excepción propia
class MyException extends
Exception
{
private int detalle;
MyException(int a)
{
detalle = a;
}
public String
toString()
{
return "MyException[" + detalle + "]";
}
}
class
ExceptionDemo
{
static
void compute(int a) throws MyException
{
System.out.println("Llamada compute(" + a + ")");
if(a > 10)
throw new MyException(a);
System.out.println("Salida normal");
}
public static
void main(String args[])
{
try
{
compute(1);
compute(20);
}
catch (MyException
e)
{
System.out.println("Capturada: " + e);
}
}
}
Notas al ejemplo de ObjGraf,
Rectangulo y Circulo
- Una excepción del tipo
BaseNegativaException lanzada durante la construcción de un objeto es causa de
que el proceso se aborte, obteniendo un valor null para el objeto que debía
ser creado.
- Es interesante notar que
si los métodos setX() y setY() realizaran validaciones y lanzaran las
correspondientes excepciones, esta lógica sería heredada por Rectangulo y
Circulo. Sin embargo para el usuario de estas clases el comportamiento sería
transparente. En caso de no suministrar la información necesaria y correcta
para crear un objeto entonces se produce una excepción independientemente
desde el constructor de ObjGraf o del de una subclase.
Depuración en Java
- Los entornos IDE de Java
disponen de la posibuilidad de establecer breakpoints interactivamente,
ejecutar paso a paso y acceder a las variables.
- El JDK posee un debugger
de línea que es menos interactivo aunque ofrece muchas posibilidades. Se
invoca llamando a jdb y ejecutando ? se obtiene la ayuda. Es bueno saber que
lo tendremos en cualquier plataforma en que haya JDK.
- Sin embargo en muchos
casos puede ser suficiente con utilizar System.out.println() que imprime en la
cónsola de sistema, la cual puede estar disponible tanto en programas de
ventanas como obviamente en programas en modo cónsola. Java la abre por
defecto siempre.
- Finalmente mencionar que
los navegadores poseen una cónsola Java que recoge los System.out.println()
que ejecute el applet. En realidad es la salida estándar del applet.
Unidad anterior - Unidad
siguiente
Copyright DENVIR STUDIOS ®
Lima - Perú, 2002