Concepto
Cuando programamos en un lenguaje escribimos instrucciones destinadas
a:
è
Implementar la solución a nuestro problema
è
Detectar y resolver las posibles situaciones
excepcionales que se puedan dar (no se encuentra un fichero, el usuario se
equivoca al escribir, fallos de red o de bases de datos…)
Es como si nuestro programa pudiera recorrer dos caminos bien
distintos. El “normal” y el “excepcional”.
Para tartar las situciaciones atípicas Java usa siempre el mismo
protocol de actuación, basado en el uso de las excepciones.
Una excepción es un evento, que ocurre durante la ejecución del
programa, y que interrumpe el flujo “normal” del mismo.
Ejemplo de excepciones:
è
No se encuentra un fichero o no se puede abrir,
leer…
è
Una operación divide por cero o accede a una
posición inexistente de una table
è
Fallos de hardware (disco duro, tarjeta de red,
video…)
Veamos el siguiente trozo de código
public class Linterna{
private
Bombilla b = null;
private
boolean encendida;
private
double porcentajeCargaPilas;
public
Linterna(){
//
Olvidamos crear la bombilla
porcentajeCargaPilas
= 100;
}
public
void encender(){
b.encender();
encendida
= true;
}
}…
public class PruebaLinterna{
public
class void main(String args[]){
Linterna
lin = new Linterna();
lin.encender();
// Provoca la excepción siguiente
}
}
Este código provocará el siguiente error: EXCEPTION IN THREAD MAIN JAVA LANG NULLPOINTEREXCEPTION…
Protocolo de actuación
Cuando ocurre algún problema en la ejecución de un método se realiza lo
siguiente:
1.
Se lanza una excepción, esto consiste en que:
a.
Se detiene el camino “normal” de la ejecución del
programa
b.
El método en el que se produjo el problema
crea un objeto (objeto excepción) que
contiene toda la información sobre el error y el estado del programa cuando
éste ocurrió.
c.
A continuación el objeto creado es enviado
(lanzado) a un modulo de la MVJ que actúa como gestor de la ejecución y que
toma el control del programa.
2.
Se intent capturer la excepción lanzada
a.
A continuación el Gestor de la Ejecución tiene que
buscar algún trozo de código capaz de manejar el error que se ha producido.
b.
Para ello, primero busca el código manejador de la
excepción en el método que lanzó la excepción.
i.
Si lo encuentra entonces tratamos el error y el
gestor de ejecución devuelve el control al programa que continua con la
siguiente instrucción.
ii.
Si no lo encuentra entonces el Gestor de la
Ejecución busca en el método que llamó al que provocó el error par aver si éste
es capaz de solucionarlo.
c.
De esta forma la excepción se va prolongando por la
cadena de método hasta que encuentra alguno que solucione el problema.
d.
Si el objeto excepción llega al método main
y tampoco es capaz de manejarlo entonces se cierra el programa y se imprime la
información de lo ocurrido.
En nuestro ejemplo ocurrió lo siguiente:
Resumiendo:
è
El método en el que se produce el error crea y
lanza (también se usa eleva o levanta) un objeto excepción al gestor de
ejecución
è
El gestor de ejecución tiene que capturer la
excepción lanzada encontrando un trozo de código capaz de manejarla en la
cadena de método llamados (propagación de la excepción).
El objeto excepción lanzado se comporta como una “patata caliente” que
pasa de mano en mano (se propaga) hasta que alguien sea capaz de aguantarla (se
captura) o si no se cae al suelo (se cierra el programa).
La jerarquía de objetos “lanzables”
Los problemas que provocan que se lancen una excepción los podemso
clasificar en dos grupos:
è
Predecibles.
Hay algunas instrucciones que por su naturaleza el compilador las
considera peligrosas, en el sentido de que se pueden lanzar una expceción. Ej:
Las instrucciones que intentan abrir un fichero pueden lanzar una excepción
porque no se encuentre el fichero o no tengamos permiso.
è
No
predecibles. Son problemas que el compilador no puede prever
porque ocurren durante la ejecución. Se deben a:
o Fallos de
programación. Se accede a un puntero nulo, se divide por cero un entero, se
accede a una posición inexistente de una table…
o Fallos
del software o del hardware del sistema. Falla la máquina virtual java o el
disco duro…
Java proporciona la siguiente jerarquía de clases:
Tratamiento de una excepción
El siguiente código no compila
import java.io.*;
public class ListaDeNumeros{
private
int vector[];
private
int tamanio = 10;
public
ListaDeNumeros{
vector
= new int[tamanio];
for
(int i = 0; i < tamanio; i++)
vector[i]
= i;
}
public
void escribeLista(){
PrintWriter
fichTexto = new PrintWriter(“Lista.txt”);
for
(int i = 0; i < tamanio; i++)
fichTexto.println(“El
valor de ” + i + “ = ” + vector[i]);
fichTexto.close();
}
}
El compilador nos dice:
<< Tipo de
excepción FileNotFundException no manejada >>
El compilador ha identificado un problema potencial (o predecible) en
el método escribeLista y nos oblige a elegir entre:
è
Afrontar
el problema. Es decir, capturer la excepción escribiendo un
trozo de código que la maneje
è
Ignorar
el problema. No escribimos código para capturer la excepción,
simplementen dejamos que se propague al siguiente método de la cadena de
llamadas.
Si elegimos afrontar
el problema entonces tenemos que capturer la excepción. Para
ello hay que encerrar la instrucción peligrosa en un bloque try
(intentar) y su solución en un bloque catch (capturar).
try{
…
Instrucción peligrosa
…
}
catch(TipoDeExcepción e){
tratamiento del problema
}
En nuestro ejemplo, ahora modifcamos escribeLista para que captrue la excepción:
…
public void escribeLista(){
try{
PrintWriter fichTexto = new
PrintWriter(“lista.txt”);
for(int i = 0; i < tamanio; i++)
fichTexto.println(“El valor de ” + i +
“ = “ + vector[i]);
fichTexto.close();
}
catch(FileNotFoundException e){
System.out.println(“Error al abrir el
fichero lista.txt”);
e.printStackTrace(); // Saca los errores
por consola de Java
}
}
Probamos la clase ListaDeNumeros con
public class
PruebaListaDeNumeros(){
public static void main(String[] args){
ListaDeNumeros lista = new ListaDeNumeros();
lista.escribeLista();
}
}
Ahora vamos a provocar el fallo. Para ello, vamos a proteger contra
escritura el fichero lista.txt y volvemos
a ejecutar.
Si elegimos ignorarlo
debemos saber que para que un método ignore una excepción hay que indicarlo en
su prototipo. En nuestro ejemplo:
public
void escribeLista() throws FileNotFoundException{
PrintWriter fichTexto = new PrintWriter(“lista.txt”);
For (int i = 0; i < tamanio; i++)
fichTexto.println(“El valor de ” + i + “ =
“ + vector[i]);
ficTexto.close();
}
Así se informa al compilador de que el método lanza y propaga la
excepción, no la captura.
Con la nueva versión de escribeLista
ahora sería main quien tiene que decidir entre las dos opciones:
Ignorar de nuevo la excepción
public
class PruebaListaDeNumeros(){
public static void main(String [] args)
throws FileNotFoundException{
ListaDeNumeros lista = new
ListaDeNumeros();
lista.escribeLista();
}
}
O capturarla
public
class PruebaListaDeNumeros(){
public static void main(String [] args){
ListaDeNumeros lista = new
ListaDeNumeros();
try{
lista.escribeLista();
}
catch(FileNotFoundException e){
System.out.println(“Error al abrir
lista.txt”);
e.printStackTrace();
}
}
}
Excepciones comprobadas y no comprobadas
El compilador sólo nos obliga a elegir entre capturar o ignorar una
excepción si ésta se corresponde con un problema predecible.
Esto divide las excepciones en dos grupos:
è
Las comprobadas por el compilador (checked
exceptions)
è
Y las no comprobadas (unchecked exceptions)
Las excepciones no comprobadas se producen en tiempo de ejecución y son
muy variadas y numerosas, si el compilador nos obligará a tratarlas se
reduciría mucho la claridad del código.
Aunque no es muy común, nada nos impide capturar o ignorar una
excepción no comprobada.
Tratamiento de varias excepciones
Cuando un bloque de código puede lanzar varias excepciones podemos:
è
Ignorarlas todas
è
Ignorar algunas y capturar todas
è
Capturarlas todas
Java permite ignorar varias excepciones comprobadas siempre que las
especifiqumos en el prototipo del método.
modVisib
tipoRetorno nombre() throws exc1, exc2, …{ … }
Ejemplo
public
void visualizar(String nombre) throws FileNotFoundException, IOException{ … }
Para capturar varias excepciones podemos escribir varios bloques catch
para un mismo try.
Sintaxis resultante
try{
// Bloque de instrucciones que puede lanzar
varias excepciones
}
catch(ClaseExcepcion1
excep1){
// Tratamiento excep1
}
catch(ClaseExcepcion2
excep2){
// Tratamiento excep2
}
catch(ClaseExcepcion3
excep3){
// Tratamiento excep3
}
Ademas los bloques try … catch se pueden anidar y repetirse tantas
veces como se quiera.
Veamos el siguiente ejemplo
import
java.io.*;
public
class ManejaFicheroTexto{
private FileReader manejadorFichero;
public void visualiza(String nombreFichero){
int carácter;
try{
// Se intenta abrir el fichero y puede
que lance un FileNotFoundException
manejadorFichero = new
FileReader(nombreFichero);
carácter = manejadorFichero.read(); //
Puede lanzar una IOException
while(caracter != -1){
System.out.write(caracter);
Carácter =
manejadorFichero.read(); // Puede lanzar un IOException
}
manejadorFichero.close(); // Puede
lanza un IOException
}
catch(FileNotFoundException e){
System.out.println(“Error al abrir el
fichero ” + nombreFichero);
}
catch(IOException){
System.out.println(“Error al leer o cerrar
el fichero” + nombreFichero);
e.printStackTrace();
}
}
}
Si se produce una excepción el Gestor de Ejecución comprueba los
bloques catch pororden de aparición preguntando si la excepción lanzada
“encaja” con algún catch.
Recordemos que:
è
El operador instanceof
devuelve cierto si el objeto comparado es una instancia de la clase indicada o
de alguna de sus subclases.
è
Por otro lado, en nuestro ejemplo tenemos la
siguiente jerarquía de clases.
Regla del orden de los bloques
catch
Debemos capturar las excepciones de las más concretas a más generales.
Otra forma de enunciar la regla sería “Debemos capturar las excepciones desde las hojas a la raíz del árbol de
objetos”.
El bloque finally
El bloque finally acompaña al
bloque try y se escribe al final de
los bloques catch (si existen).
Sintaxis:
try{
…
}
finally{
…
}
try{
…
}
catch(tipoExcepcion
e1){
…
}
catch(tipoExcepcion
e2){
…
}
finally{
…
}
El bloque finally se
ejecutará siempre que se ejecute el bloque try
que acompaña, se produza o no una excepción.
El útil cuando queremso cerrar recursos (ficheros, conexiones a una
base de datos). En nuestra clase ManejaFicherosTexto
podríamsos hacer:
public
void visualiza(String nombreFichero){
int caracter;
try{
manejadorFichero = new
FileReader(nombreFichero);
caracter = manejadorFichero.read();
while (caracter != -1){
System.out.write(caracter);
caracter = manejadorFichero.read();
}
}
catch(FileNotFoundException e){
System.out.println(“Error al leer el
fichero ” + nombreFichero);
}
catch(IOException e){
System.out.println(“Error al leer o cerrar
el fichero” + nombreFichero);
e.printStackTrace();
}
finally{
if(manejadorFichero != null)
manejadorFichero.close();
}
}
La creación de excepciones de usuario
Al crear una clase tenemos que pensar tanto en su funcionalidad como en
las posibles situaciones anómalas o malos usos a los que se puede enfrentar.
Java permite crear y lanzar excepciones propias del usuario simplemente
heredando de Exception o de alguna de sus subclases.
De esta forma durante el desarrollo de un sistema debemos pensar en que
excepciones vamos a utilizar, cómo se podrían jerarquizar y en que condiciones
se van a lanzar.
Seguiremos los siguientes pasos:
1.
Identificar las
excepciones que necesitamos. Si hay una condición de los parámetros de
entrada de un método o del estado del objeto que impide que dicho método se
ejecute de forma correcta estamos ante una situación en la que posiblemente se
lance una excepción.
2.
Escoger un
nombre significativo. Para respetar el convenio se sigue la librería de
clases de Java, las excepciones de usuario se suelen terminar con la palabra Exception.
Ejemplos:
NumeroTarjetaInvalidoException, DniIncorrectoException,
CocheSinCombustibleException…
3.
Elegir la clase
de la que vamos a heredar y crear la nueva clase excepción. Tenemos que
escoger a Exception o alguna de sus subclases como superclase de nuestra
excepción. La elección de una u otra dependerá de la naturaleza de la excepción
a crear. Sería conveniente estudiar si tiene sentida crear una superclase común
a todas nuestras excepciones.
4.
Para lanzar la excepción desde un método debemos crear el objeto excepción y después
lanzarlo. Ejemplo:
DniIncorrectoException
e = new DniIncorrectoException{ … };
Throw
e;
Aunque
es más común escribirlo todo en una línea
throw
new DniIncorrectoException{ … };
Volvemos con la clase Bombilla para poner un ejemplo.
Ahora queremos que nuestra clase pueda ser utilizada por otras clases y
sean éstas las que decidan qué hacer en caso de errores / excepciones.
Siguiendo los anteriores pasos, nuestro código quedaría
public
class BombillaFundidaException extends Exception{
// Creamos dos versiones del constructor de
la clase
public BombillaFundidaException(){}
public BombillaFundidaException(String
mensaje){
super(mensaje); // Llamamos al constructor
del padre
}
// Heredamos el resto de métodos
}
public
class Bombilla{
private int potencia;
private int numEncendidos;
private boolean encendida;
private boolean fundida;
public Bombilla(int potencia){
this.potencia = potencia;
}
public void apagar() throws
BombillaFundidaException{
if (fundida == true)
throw new BombillaFundidaException();
else
encendida = false;
}
public void encender() throws
BombillaFundidaException{
if (fundida = true)
throw new BombillaFundidaException();
else{
if (encendida == false){
numEncendidos ++;
if (numEncendidos == 1000){
fundida = true;
encendida = false;
throw new
BombillaFundidaException(“Recien fundida”);
}
}
}
}
public boolean estaFundida(){
return fundida
}
}
Ahora al usar los métodos “peligrosos” el compilador nos obligará a
capturar la excepción o ignorarla.
public
class PruebaBombilla{
public static void main(String args[]){
Bombilla b = new Bombilla(100);
try{
b.encender();
b.apagar();
}
catch(BombillaFundidaException e){
System.out.println(“Bombilla fundida”);
// SI hay mensaje hacer un getMensaje
}
…
}
}
Consideraciones sobre las excepciones
Veamos algunas observaciones a tener en cuenta en el uso de
excepciones:
è
Si creamos una excepción de usuario y hacemos
que extienda la clase RuntimeException entonces el compilador la considerará
una excepción no comprobada y no nos obligará a capturarla o ignorarla.
Esto
es posible, pero no recomendable, ya que obtenemos un código menos seguro.
è
Los constructores pueden lanzar excepciones
è
Los métodos especificados en una interfaz pueden
lanzar excepciones
è
Si tenemos una relación de herencia entre dos
clases, la subclase hereda los métodos con las excepciones que lanzan.
è
Si la subclase sobrescribe un método, lanza una
excepción lanzanza siempre que ésta sea una subclase de la excepción
especificada por el método de la superclase
Ventajas del uso de excepciones
Permite separar el código normal del código de manejo de errores,
mejorando la legibilidad y comprensión del código (evitamos códigos muy
anidados).
Se agrupan los errores en una jerarquía de clases de manera que permite
la captura y tratamiento por grupos.
Además se evite el diseño y uso de los códigos de error que es un
mecanismo bastante tedioso y propenso a provocar fallos de programación.
La propagación de una excepción lanzada por la cadena de llamadas
permite que la capturemos en el método que queramos. De esta forma podemos
distinguir métodos que se preocupan o no de los errores.
Permite al usuario crear sus propias excepciones mediante la herencia,
de forma que se estandariza la forma de tratar los errores para cualquier
clase.
0 comentarios:
Publicar un comentario